Browse Source

Refactored logging to not rely on static access and a global context.

pull/187/head
Eric Kok 10 years ago
parent
commit
04a9da420f
  1. 1
      app/build.gradle
  2. 2
      app/src/main/AndroidManifest.xml
  3. 50
      app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java
  4. 30
      app/src/main/java/org/transdroid/core/gui/DetailsActivity.java
  5. 12
      app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
  6. 3
      app/src/main/java/org/transdroid/core/gui/ServerStatusView.java
  7. 62
      app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
  8. 10
      app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
  9. 2
      app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java
  10. 6
      app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java
  11. 46
      app/src/main/java/org/transdroid/core/gui/log/Log.java
  12. 7
      app/src/main/java/org/transdroid/core/gui/log/LogUncaughtExceptionHandler.java
  13. 2
      app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java
  14. 7
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java
  15. 2
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java
  16. 151
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java
  17. 90
      app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java
  18. 187
      app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java
  19. 251
      app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java
  20. 4
      app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java
  21. 8
      app/src/main/java/org/transdroid/core/seedbox/XirvikSharedSettingsActivity.java
  22. 15
      app/src/main/java/org/transdroid/core/service/AppUpdateService.java
  23. 52
      app/src/main/java/org/transdroid/core/service/BootReceiver.java
  24. 50
      app/src/main/java/org/transdroid/core/service/ControlService.java
  25. 50
      app/src/main/java/org/transdroid/core/service/RssCheckerService.java
  26. 10
      app/src/main/java/org/transdroid/core/service/ServerCheckerService.java
  27. 176
      app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java
  28. 111
      app/src/main/java/org/transdroid/core/widget/ListWidgetProvider.java
  29. 66
      app/src/main/java/org/transdroid/core/widget/ListWidgetViewsService.java
  30. 16
      app/src/main/java/org/transdroid/daemon/AlphanumComparator.java
  31. 130
      app/src/main/java/org/transdroid/daemon/Aria2c/Aria2Adapter.java
  32. 311
      app/src/main/java/org/transdroid/daemon/BitComet/BitCometAdapter.java
  33. 49
      app/src/main/java/org/transdroid/daemon/Bitflu/BitfluAdapter.java
  34. 71
      app/src/main/java/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java
  35. 135
      app/src/main/java/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java
  36. 4
      app/src/main/java/org/transdroid/daemon/Daemon.java
  37. 172
      app/src/main/java/org/transdroid/daemon/Deluge/DelugeAdapter.java
  38. 10
      app/src/main/java/org/transdroid/daemon/DummyAdapter.java
  39. 3
      app/src/main/java/org/transdroid/daemon/IDaemonAdapter.java
  40. 182
      app/src/main/java/org/transdroid/daemon/Ktorrent/KtorrentAdapter.java
  41. 95
      app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java
  42. 170
      app/src/main/java/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java
  43. 271
      app/src/main/java/org/transdroid/daemon/Synology/SynologyAdapter.java
  44. 181
      app/src/main/java/org/transdroid/daemon/TaskQueue.java
  45. 39
      app/src/main/java/org/transdroid/daemon/Tfb4rt/Tfb4rtAdapter.java
  46. 4
      app/src/main/java/org/transdroid/daemon/TorrentFilesComparator.java
  47. 20
      app/src/main/java/org/transdroid/daemon/TorrentsComparator.java
  48. 179
      app/src/main/java/org/transdroid/daemon/Transmission/TransmissionAdapter.java
  49. 275
      app/src/main/java/org/transdroid/daemon/Utorrent/UtorrentAdapter.java
  50. 28
      app/src/main/java/org/transdroid/daemon/Vuze/VuzeAdapter.java
  51. 66
      app/src/main/java/org/transdroid/daemon/task/DaemonTask.java
  52. 7
      app/src/main/java/org/transdroid/daemon/util/Collections2.java
  53. 59
      app/src/main/java/org/transdroid/daemon/util/DLog.java
  54. 154
      app/src/main/java/org/transdroid/daemon/util/HttpHelper.java
  55. 44
      app/src/main/java/org/transdroid/daemon/util/ITLogger.java
  56. 77
      app/src/main/java/org/transdroid/daemon/util/Pair.java
  57. 15
      app/src/main/java/org/transdroid/daemon/util/SelfSignedTrustManager.java
  58. 3
      app/src/main/java/org/transdroid/daemon/util/TlsSniSocketFactory.java

1
app/build.gradle

@ -49,6 +49,7 @@ dependencies {
compile 'com.github.chrisbanes.actionbarpulltorefresh:library:0.8' compile 'com.github.chrisbanes.actionbarpulltorefresh:library:0.8'
compile 'de.keyboardsurfer.android.widget:crouton:1.8.+' compile 'de.keyboardsurfer.android.widget:crouton:1.8.+'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.+' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.+'
compile 'com.android.support:support-annotations:20.0.0'
apt "org.androidannotations:androidannotations:3.1" apt "org.androidannotations:androidannotations:3.1"
} }

2
app/src/main/AndroidManifest.xml

@ -245,7 +245,7 @@
<service android:name="org.transdroid.core.service.AppUpdateService_" /> <service android:name="org.transdroid.core.service.AppUpdateService_" />
<receiver android:name="org.transdroid.core.service.AlarmReceiver_" /> <receiver android:name="org.transdroid.core.service.AlarmReceiver_" />
<receiver android:name="org.transdroid.core.service.BootReceiver" > <receiver android:name="org.transdroid.core.service.BootReceiver_" >
<intent-filter> <intent-filter>
<action <action
android:name="android.intent.action.BOOT_COMPLETED" android:name="android.intent.action.BOOT_COMPLETED"

50
app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java

@ -16,17 +16,17 @@
*/ */
package org.transdroid.core.app.settings; package org.transdroid.core.app.settings;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import org.transdroid.core.gui.lists.SimpleListItem; import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.*;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.OS; import org.transdroid.daemon.OS;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
/** /**
* Represents a user-configured remote server. * Represents a user-configured remote server.
* @author Eric Kok * @author Eric Kok
@ -81,10 +81,10 @@ public class ServerSetting implements SimpleListItem {
* @param isAutoGenerated Whether this setting was generated rather than manually inputed by the user * @param isAutoGenerated Whether this setting was generated rather than manually inputed by the user
*/ */
public ServerSetting(int key, String name, Daemon type, String address, String localAddress, int localPort, public ServerSetting(int key, String name, Daemon type, String address, String localAddress, int localPort,
String localNetwork, int port, boolean ssl, boolean sslTrustAll, String sslTrustKey, String folder, String localNetwork, int port, boolean ssl, boolean sslTrustAll, String sslTrustKey,
boolean useAuthentication, String username, String password, String extraPass, OS os, String downloadDir, String folder, boolean useAuthentication, String username, String password, String extraPass,
String ftpUrl, String ftpPassword, int timeout, boolean alarmOnFinishedDownload, boolean alarmOnNewTorrent, OS os, String downloadDir, String ftpUrl, String ftpPassword, int timeout,
boolean isAutoGenerated) { boolean alarmOnFinishedDownload, boolean alarmOnNewTorrent, boolean isAutoGenerated) {
this.key = key; this.key = key;
this.name = name; this.name = name;
this.type = type; this.type = type;
@ -113,8 +113,9 @@ public class ServerSetting implements SimpleListItem {
@Override @Override
public String getName() { public String getName() {
if (!TextUtils.isEmpty(name)) if (!TextUtils.isEmpty(name)) {
return name; return name;
}
if (!TextUtils.isEmpty(address)) { if (!TextUtils.isEmpty(address)) {
String host = Uri.parse(address).getHost(); String host = Uri.parse(address).getHost();
return host == null ? DEFAULT_NAME : host; return host == null ? DEFAULT_NAME : host;
@ -221,13 +222,13 @@ public class ServerSetting implements SimpleListItem {
public String getHumanReadableIdentifier() { public String getHumanReadableIdentifier() {
if (isAutoGenerated) { if (isAutoGenerated) {
// Hide the 'implementation details'; just give the username and server // Hide the 'implementation details'; just give the username and server
return (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ? this.getUsername() + "@" return (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ?
: "") + getAddress(); this.getUsername() + "@" : "") + getAddress();
} }
return (this.ssl ? "https://" : "http://") return (this.ssl ? "https://" : "http://") +
+ (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ? this.getUsername() + "@" (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ? this.getUsername() + "@" :
: "") + getAddress() + ":" + getPort() "") + getAddress() + ":" + getPort() +
+ (Daemon.supportsCustomFolder(getType()) && getFolder() != null ? getFolder() : ""); (Daemon.supportsCustomFolder(getType()) && getFolder() != null ? getFolder() : "");
} }
/** /**
@ -238,8 +239,9 @@ public class ServerSetting implements SimpleListItem {
* user name; returns null if the server is not yet fully identifiable (during configuration, for example) * user name; returns null if the server is not yet fully identifiable (during configuration, for example)
*/ */
public String getUniqueIdentifier() { public String getUniqueIdentifier() {
if (getType() == null || getAddress() == null || getAddress().equals("")) if (getType() == null || getAddress() == null || getAddress().equals("")) {
return null; return null;
}
return getType().toString() + "|" + getHumanReadableIdentifier(); return getType().toString() + "|" + getHumanReadableIdentifier();
} }
@ -261,7 +263,7 @@ public class ServerSetting implements SimpleListItem {
* Returns the appropriate daemon adapter to which tasks can be executed, in accordance with this server's settings * Returns the appropriate daemon adapter to which tasks can be executed, in accordance with this server's settings
* @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not
* be determined * be determined
* @param context * @param context A context to access the logger
* @return An IDaemonAdapter instance of the specific torrent client daemon type * @return An IDaemonAdapter instance of the specific torrent client daemon type
*/ */
public IDaemonAdapter createServerAdapter(String connectedToNetwork, Context context) { public IDaemonAdapter createServerAdapter(String connectedToNetwork, Context context) {
@ -279,13 +281,15 @@ public class ServerSetting implements SimpleListItem {
// The local integer key is converted to the idString string. // The local integer key is converted to the idString string.
// The host name address used is dependent on the network that we are currently connected to (to allow a // The host name address used is dependent on the network that we are currently connected to (to allow a
// distinct connection IP or host name when connected to a local network). // distinct connection IP or host name when connected to a local network).
if (!TextUtils.isEmpty(localNetwork)) if (!TextUtils.isEmpty(localNetwork)) {
Log.d(caller, "Creating adapter for " + name + " of type " + type.name() + ": connected to " Log_.getInstance_(caller)
+ connectedToNetwork + " and configured local network is " + localNetwork); .d("ServerSetting", "Creating adapter for " + name + " of type " + type.name() + ": connected to " +
connectedToNetwork + " and configured local network is " + localNetwork);
}
String addressToUse = address; String addressToUse = address;
int portToUse = port; int portToUse = port;
if (!TextUtils.isEmpty(localNetwork) && !TextUtils.isEmpty(localAddress) if (!TextUtils.isEmpty(localNetwork) && !TextUtils.isEmpty(localAddress) &&
&& !TextUtils.isEmpty(connectedToNetwork)) { !TextUtils.isEmpty(connectedToNetwork)) {
String[] localNetworks = localNetwork.split("\\|"); String[] localNetworks = localNetwork.split("\\|");
for (String network : localNetworks) { for (String network : localNetworks) {
if (connectedToNetwork.equals(network)) { if (connectedToNetwork.equals(network)) {

30
app/src/main/java/org/transdroid/core/gui/DetailsActivity.java

@ -95,6 +95,8 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
// Settings // Settings
@Bean @Bean
protected Log log;
@Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@Bean @Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@ -185,7 +187,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Background @Background
protected void refreshTorrent() { protected void refreshTorrent() {
DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(); DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log);
if (result instanceof RetrieveTaskSuccessResult) { if (result instanceof RetrieveTaskSuccessResult) {
onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(),
((RetrieveTaskSuccessResult) result).getLabels()); ((RetrieveTaskSuccessResult) result).getLabels());
@ -198,7 +200,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
public void refreshTorrentDetails(Torrent torrent) { public void refreshTorrentDetails(Torrent torrent) {
if (!Daemon.supportsFineDetails(torrent.getDaemon())) if (!Daemon.supportsFineDetails(torrent.getDaemon()))
return; return;
DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log);
if (result instanceof GetTorrentDetailsTaskSuccessResult) { if (result instanceof GetTorrentDetailsTaskSuccessResult) {
onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails()); onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails());
} else { } else {
@ -210,7 +212,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
public void refreshTorrentFiles(Torrent torrent) { public void refreshTorrentFiles(Torrent torrent) {
if (!Daemon.supportsFileListing(torrent.getDaemon())) if (!Daemon.supportsFileListing(torrent.getDaemon()))
return; return;
DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log);
if (result instanceof GetFileListTaskSuccessResult) { if (result instanceof GetFileListTaskSuccessResult) {
onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles()); onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles());
} else { } else {
@ -222,7 +224,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Override @Override
public void resumeTorrent(Torrent torrent) { public void resumeTorrent(Torrent torrent) {
torrent.mimicResume(); torrent.mimicResume();
DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName()));
} else { } else {
@ -234,7 +236,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Override @Override
public void pauseTorrent(Torrent torrent) { public void pauseTorrent(Torrent torrent) {
torrent.mimicPause(); torrent.mimicPause();
DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName()));
} else { } else {
@ -246,7 +248,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Override @Override
public void startTorrent(Torrent torrent, boolean forced) { public void startTorrent(Torrent torrent, boolean forced) {
torrent.mimicStart(); torrent.mimicStart();
DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(); DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName()));
} else { } else {
@ -258,7 +260,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Override @Override
public void stopTorrent(Torrent torrent) { public void stopTorrent(Torrent torrent) {
torrent.mimicStop(); torrent.mimicStop();
DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName()));
} else { } else {
@ -269,7 +271,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Background @Background
@Override @Override
public void removeTorrent(Torrent torrent, boolean withData) { public void removeTorrent(Torrent torrent, boolean withData) {
DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(); DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
// Close the details activity (as the torrent is now removed) // Close the details activity (as the torrent is now removed)
closeActivity(getString(withData ? R.string.result_removed_with_data : R.string.result_removed, closeActivity(getString(withData ? R.string.result_removed_with_data : R.string.result_removed,
@ -293,7 +295,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
public void updateLabel(Torrent torrent, String newLabel) { public void updateLabel(Torrent torrent, String newLabel) {
torrent.mimicNewLabel(newLabel); torrent.mimicNewLabel(newLabel);
DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel) DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel)
.execute(); .execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_labelset, newLabel)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_labelset, newLabel));
} else { } else {
@ -305,7 +307,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Override @Override
public void forceRecheckTorrent(Torrent torrent) { public void forceRecheckTorrent(Torrent torrent) {
torrent.mimicCheckingStatus(); torrent.mimicCheckingStatus();
DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, onTaskSucceeded((DaemonTaskSuccessResult) result,
getString(R.string.result_recheckedstarted, torrent.getName())); getString(R.string.result_recheckedstarted, torrent.getName()));
@ -317,7 +319,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Background @Background
@Override @Override
public void updateTrackers(Torrent torrent, List<String> newTrackers) { public void updateTrackers(Torrent torrent, List<String> newTrackers) {
DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(); DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated));
} else { } else {
@ -328,7 +330,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Background @Background
@Override @Override
public void updateLocation(Torrent torrent, String newLocation) { public void updateLocation(Torrent torrent, String newLocation) {
DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(); DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation));
} else { } else {
@ -340,7 +342,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Override @Override
public void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority) { public void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority) {
DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority,
new ArrayList<TorrentFile>(files)).execute(); new ArrayList<TorrentFile>(files)).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset));
} else { } else {
@ -373,7 +375,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@UiThread @UiThread
protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) { protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) {
Log.i(this, result.getException().toString()); log.i(this, result.getException().toString());
String error = getString(LocalTorrent.getResourceForDaemonException(result.getException())); String error = getString(LocalTorrent.getResourceForDaemonException(result.getException()));
fragmentDetails.updateIsLoading(false, isCritical ? error : null); fragmentDetails.updateIsLoading(false, isCritical ? error : null);
Crouton.showText(this, getString(LocalTorrent.getResourceForDaemonException(result.getException())), Crouton.showText(this, getString(LocalTorrent.getResourceForDaemonException(result.getException())),

12
app/src/main/java/org/transdroid/core/gui/DetailsFragment.java

@ -29,16 +29,10 @@ import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.*;
import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.lists.DetailsAdapter; import org.transdroid.core.gui.lists.DetailsAdapter;
import org.transdroid.core.gui.lists.SimpleListItemAdapter; import org.transdroid.core.gui.lists.SimpleListItemAdapter;
import org.transdroid.core.gui.navigation.Label; import org.transdroid.core.gui.navigation.*;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.navigation.NavigationHelper_;
import org.transdroid.core.gui.navigation.RefreshableActivity;
import org.transdroid.core.gui.navigation.SelectionManagerMode;
import org.transdroid.core.gui.navigation.SetLabelDialog;
import org.transdroid.core.gui.navigation.SetLabelDialog.OnLabelPickedListener; import org.transdroid.core.gui.navigation.SetLabelDialog.OnLabelPickedListener;
import org.transdroid.core.gui.navigation.SetStorageLocationDialog; import org.transdroid.core.gui.navigation.SetStorageLocationDialog;
import org.transdroid.core.gui.navigation.SetStorageLocationDialog.OnStorageLocationUpdatedListener; import org.transdroid.core.gui.navigation.SetStorageLocationDialog.OnStorageLocationUpdatedListener;
@ -180,7 +174,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
/** /**
* Updates the list adapter to show a new list of torrent files, replacing the old files list. * Updates the list adapter to show a new list of torrent files, replacing the old files list.
* @param checkTorrent The torrent for which the details were retrieved * @param checkTorrent The torrent for which the details were retrieved
* @param newTorrents The new, updated list of torrent file objects * @param newTorrentFiles The new, updated list of torrent file objects
*/ */
public void updateTorrentFiles(Torrent checkTorrent, ArrayList<TorrentFile> newTorrentFiles) { public void updateTorrentFiles(Torrent checkTorrent, ArrayList<TorrentFile> newTorrentFiles) {
// Check if these are actually the details of the torrent we are now showing // Check if these are actually the details of the torrent we are now showing

3
app/src/main/java/org/transdroid/core/gui/ServerStatusView.java

@ -54,7 +54,7 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
/** /**
* Updates the statistics as shown in the action bar through this server status view. * Updates the statistics as shown in the action bar through this server status view.
* @param torrents The most recently received list of torrents * @param torrents The most recently received list of torrents
* @param dormantAsInactive * @param dormantAsInactive Whether to treat dormant (0KB/s) torrent as inactive state torrents
*/ */
public void update(List<Torrent> torrents, boolean dormantAsInactive) { public void update(List<Torrent> torrents, boolean dormantAsInactive) {
@ -66,6 +66,7 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
downcountSign.setVisibility(View.INVISIBLE); downcountSign.setVisibility(View.INVISIBLE);
upcountSign.setVisibility(View.INVISIBLE); upcountSign.setVisibility(View.INVISIBLE);
speedswrapperLayout.setOnClickListener(null); speedswrapperLayout.setOnClickListener(null);
return;
} }
int downcount = 0, upcount = 0, downspeed = 0, upspeed = 0; int downcount = 0, upcount = 0, downspeed = 0, upspeed = 0;

62
app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java

@ -92,7 +92,6 @@ import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.task.StartTask; import org.transdroid.daemon.task.StartTask;
import org.transdroid.daemon.task.StopTask; import org.transdroid.daemon.task.StopTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher; import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher;
@ -136,6 +135,8 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
// Navigation components // Navigation components
@Bean @Bean
protected Log log;
@Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@Bean @Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@ -231,9 +232,6 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
} }
actionBar.setListNavigationCallbacks(navigationSpinnerAdapter, this); actionBar.setListNavigationCallbacks(navigationSpinnerAdapter, this);
// Log messages from the server daemons using our singleton logger
DLog.setLogger(Log_.getInstance_(this));
// Load the default server or a server that was explicitly supplied in the starting intent // Load the default server or a server that was explicitly supplied in the starting intent
ServerSetting defaultServer = applicationSettings.getDefaultServer(); ServerSetting defaultServer = applicationSettings.getDefaultServer();
if (defaultServer == null) { if (defaultServer == null) {
@ -246,7 +244,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
// A server settings order ID was provided in this org.transdroid.START_SERVER action intent // A server settings order ID was provided in this org.transdroid.START_SERVER action intent
int serverId = getIntent().getExtras().getInt(ListWidgetProvider.EXTRA_SERVER); int serverId = getIntent().getExtras().getInt(ListWidgetProvider.EXTRA_SERVER);
if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) { if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) {
Log.e(this, "Tried to start with " + ListWidgetProvider.EXTRA_SERVER + " intent but " + serverId log.e(this, "Tried to start with " + ListWidgetProvider.EXTRA_SERVER + " intent but " + serverId
+ " is not an existing server order id"); + " is not an existing server order id");
} else { } else {
defaultServer = applicationSettings.getServerSetting(serverId); defaultServer = applicationSettings.getServerSetting(serverId);
@ -853,7 +851,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
protected void refreshTorrents() { protected void refreshTorrents() {
String startConnectionId = currentConnection.getSettings().getIdString(); String startConnectionId = currentConnection.getSettings().getIdString();
DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(); DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log);
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
// During the command execution the user changed the server, so we are no longer interested in the result // During the command execution the user changed the server, so we are no longer interested in the result
return; return;
@ -871,7 +869,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
if (!Daemon.supportsFineDetails(currentConnection.getType())) if (!Daemon.supportsFineDetails(currentConnection.getType()))
return; return;
String startConnectionId = currentConnection.getSettings().getIdString(); String startConnectionId = currentConnection.getSettings().getIdString();
DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log);
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
// During the command execution the user changed the server, so we are no longer interested in the result // During the command execution the user changed the server, so we are no longer interested in the result
return; return;
@ -888,7 +886,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
if (!Daemon.supportsFileListing(currentConnection.getType())) if (!Daemon.supportsFileListing(currentConnection.getType()))
return; return;
String startConnectionId = currentConnection.getSettings().getIdString(); String startConnectionId = currentConnection.getSettings().getIdString();
DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log);
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
// During the command execution the user changed the server, so we are no longer interested in the result // During the command execution the user changed the server, so we are no longer interested in the result
return; return;
@ -903,7 +901,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
protected void getAdditionalStats() { protected void getAdditionalStats() {
String startConnectionId = currentConnection.getSettings().getIdString(); String startConnectionId = currentConnection.getSettings().getIdString();
DaemonTaskResult result = GetStatsTask.create(currentConnection).execute(); DaemonTaskResult result = GetStatsTask.create(currentConnection).execute(log);
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
// During the command execution the user changed the server, so we are no longer interested in the result // During the command execution the user changed the server, so we are no longer interested in the result
return; return;
@ -918,7 +916,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
protected void updateTurtleMode(boolean enable) { protected void updateTurtleMode(boolean enable) {
String startConnectionId = currentConnection.getSettings().getIdString(); String startConnectionId = currentConnection.getSettings().getIdString();
DaemonTaskResult result = SetAlternativeModeTask.create(currentConnection, enable).execute(); DaemonTaskResult result = SetAlternativeModeTask.create(currentConnection, enable).execute(log);
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
// During the command execution the user changed the server, so we are no longer interested in the result // During the command execution the user changed the server, so we are no longer interested in the result
return; return;
@ -933,7 +931,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
public void addTorrentByUrl(String url, String title) { public void addTorrentByUrl(String url, String title) {
DaemonTaskResult result = AddByUrlTask.create(currentConnection, url, title).execute(); DaemonTaskResult result = AddByUrlTask.create(currentConnection, url, title).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
refreshTorrents(); refreshTorrents();
@ -944,7 +942,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
public void addTorrentByMagnetUrl(String url, String title) { public void addTorrentByMagnetUrl(String url, String title) {
DaemonTaskResult result = AddByMagnetUrlTask.create(currentConnection, url).execute(); DaemonTaskResult result = AddByMagnetUrlTask.create(currentConnection, url).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
refreshTorrents(); refreshTorrents();
@ -955,7 +953,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
protected void addTorrentByFile(String localFile, String title) { protected void addTorrentByFile(String localFile, String title) {
DaemonTaskResult result = AddByFileTask.create(currentConnection, localFile).execute(); DaemonTaskResult result = AddByFileTask.create(currentConnection, localFile).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
refreshTorrents(); refreshTorrents();
@ -971,10 +969,10 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
addTorrentFromStream(getContentResolver().openInputStream(contentUri), title); addTorrentFromStream(getContentResolver().openInputStream(contentUri), title);
} catch (SecurityException e) { } catch (SecurityException e) {
// No longer access to this file // No longer access to this file
Log.e(this, "No access given to " + contentUri.toString() + ": " + e.toString()); log.e(this, "No access given to " + contentUri.toString() + ": " + e.toString());
Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Log.e(this, contentUri.toString() + " does not exist: " + e.toString()); log.e(this, contentUri.toString() + " does not exist: " + e.toString());
Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
} }
} }
@ -986,7 +984,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
InputStream input = SearchHelper_.getInstance_(this).getFile(source, url); InputStream input = SearchHelper_.getInstance_(this).getFile(source, url);
addTorrentFromStream(input, title); addTorrentFromStream(input, title);
} catch (Exception e) { } catch (Exception e) {
Log.e(this, "Can't download private site torrent " + url + " from " + source + ": " + e.toString()); log.e(this, "Can't download private site torrent " + url + " from " + source + ": " + e.toString());
Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
} }
@ -1014,7 +1012,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED
|| response.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN || response.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN
|| response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) { || response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
Log.e(this, "Can't retrieve web torrent " + url + ": Unexpected HTTP response status code " log.e(this, "Can't retrieve web torrent " + url + ": Unexpected HTTP response status code "
+ response.getStatusLine().toString()); + response.getStatusLine().toString());
Crouton.showText(this, R.string.error_401, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, R.string.error_401, NavigationHelper.CROUTON_ERROR_STYLE);
return; return;
@ -1022,7 +1020,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
InputStream input = response.getEntity().getContent(); InputStream input = response.getEntity().getContent();
addTorrentFromStream(input, title); addTorrentFromStream(input, title);
} catch (Exception e) { } catch (Exception e) {
Log.e(this, "Can't retrieve web torrent " + url + ": " + e.toString()); log.e(this, "Can't retrieve web torrent " + url + ": " + e.toString());
Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
} }
} }
@ -1047,14 +1045,14 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
output.close(); output.close();
} }
} catch (IOException e) { } catch (IOException e) {
Log.e(this, "Can't write input stream to " + tempFile.toString() + ": " + e.toString()); log.e(this, "Can't write input stream to " + tempFile.toString() + ": " + e.toString());
Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
} finally { } finally {
try { try {
if (input != null) if (input != null)
input.close(); input.close();
} catch (IOException e) { } catch (IOException e) {
Log.e(this, "Error closing the input stream " + tempFile.toString() + ": " + e.toString()); log.e(this, "Error closing the input stream " + tempFile.toString() + ": " + e.toString());
Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, R.string.error_torrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
} }
} }
@ -1064,7 +1062,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Override @Override
public void resumeTorrent(Torrent torrent) { public void resumeTorrent(Torrent torrent) {
torrent.mimicResume(); torrent.mimicResume();
DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName()));
} else { } else {
@ -1076,7 +1074,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Override @Override
public void pauseTorrent(Torrent torrent) { public void pauseTorrent(Torrent torrent) {
torrent.mimicPause(); torrent.mimicPause();
DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName()));
} else { } else {
@ -1088,7 +1086,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Override @Override
public void startTorrent(Torrent torrent, boolean forced) { public void startTorrent(Torrent torrent, boolean forced) {
torrent.mimicStart(); torrent.mimicStart();
DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(); DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName()));
} else { } else {
@ -1100,7 +1098,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Override @Override
public void stopTorrent(Torrent torrent) { public void stopTorrent(Torrent torrent) {
torrent.mimicStop(); torrent.mimicStop();
DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName())); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName()));
} else { } else {
@ -1111,7 +1109,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
@Override @Override
public void removeTorrent(Torrent torrent, boolean withData) { public void removeTorrent(Torrent torrent, boolean withData) {
DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(); DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded( onTaskSucceeded(
(DaemonTaskSuccessResult) result, (DaemonTaskSuccessResult) result,
@ -1126,7 +1124,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
public void updateLabel(Torrent torrent, String newLabel) { public void updateLabel(Torrent torrent, String newLabel) {
torrent.mimicNewLabel(newLabel); torrent.mimicNewLabel(newLabel);
DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel) DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel)
.execute(); .execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded( onTaskSucceeded(
(DaemonTaskSuccessResult) result, (DaemonTaskSuccessResult) result,
@ -1141,7 +1139,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Override @Override
public void forceRecheckTorrent(Torrent torrent) { public void forceRecheckTorrent(Torrent torrent) {
torrent.mimicCheckingStatus(); torrent.mimicCheckingStatus();
DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(); DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, onTaskSucceeded((DaemonTaskSuccessResult) result,
getString(R.string.result_recheckedstarted, torrent.getName())); getString(R.string.result_recheckedstarted, torrent.getName()));
@ -1153,7 +1151,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
@Override @Override
public void updateTrackers(Torrent torrent, List<String> newTrackers) { public void updateTrackers(Torrent torrent, List<String> newTrackers) {
DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(); DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated));
} else { } else {
@ -1164,7 +1162,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
@Override @Override
public void updateLocation(Torrent torrent, String newLocation) { public void updateLocation(Torrent torrent, String newLocation) {
DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(); DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation));
} else { } else {
@ -1176,7 +1174,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Override @Override
public void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority) { public void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority) {
DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority,
new ArrayList<TorrentFile>(files)).execute(); new ArrayList<TorrentFile>(files)).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset));
} else { } else {
@ -1187,7 +1185,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@Background @Background
public void updateMaxSpeeds(Integer maxDownloadSpeed, Integer maxUploadSpeed) { public void updateMaxSpeeds(Integer maxDownloadSpeed, Integer maxUploadSpeed) {
DaemonTaskResult result = SetTransferRatesTask.create(currentConnection, maxUploadSpeed, maxDownloadSpeed) DaemonTaskResult result = SetTransferRatesTask.create(currentConnection, maxUploadSpeed, maxDownloadSpeed)
.execute(); .execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_maxspeedsset)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_maxspeedsset));
} else { } else {
@ -1204,7 +1202,7 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@UiThread @UiThread
protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) { protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) {
Log.i(this, result.getException().toString()); log.i(this, result.getException().toString());
String error = getString(LocalTorrent.getResourceForDaemonException(result.getException())); String error = getString(LocalTorrent.getResourceForDaemonException(result.getException()));
Crouton.showText(this, error, NavigationHelper.CROUTON_ERROR_STYLE); Crouton.showText(this, error, NavigationHelper.CROUTON_ERROR_STYLE);
fragmentTorrents.updateIsLoading(false); fragmentTorrents.updateIsLoading(false);

10
app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java

@ -31,8 +31,7 @@ import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.ApplicationSettings; import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.SystemSettings; import org.transdroid.core.app.settings.SystemSettings;
import org.transdroid.core.gui.lists.TorrentsAdapter; import org.transdroid.core.gui.lists.*;
import org.transdroid.core.gui.lists.TorrentsAdapter_;
import org.transdroid.core.gui.navigation.Label; import org.transdroid.core.gui.navigation.Label;
import org.transdroid.core.gui.navigation.NavigationFilter; import org.transdroid.core.gui.navigation.NavigationFilter;
import org.transdroid.core.gui.navigation.RefreshableActivity; import org.transdroid.core.gui.navigation.RefreshableActivity;
@ -158,7 +157,8 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
/** /**
* Clears the currently visible list of torrents. * Clears the currently visible list of torrents.
* @param b * @param clearError Also clear any error message
* @param clearFilter Also clear any selected filter
*/ */
public void clear(boolean clearError, boolean clearFilter) { public void clear(boolean clearError, boolean clearFilter) {
this.torrents = null; this.torrents = null;
@ -213,14 +213,14 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
// Filter the list of torrents to show according to navigation and text filters // Filter the list of torrents to show according to navigation and text filters
ArrayList<Torrent> filteredTorrents = new ArrayList<Torrent>(torrents); ArrayList<Torrent> filteredTorrents = new ArrayList<Torrent>(torrents);
if (filteredTorrents != null && currentNavigationFilter != null) { if (currentNavigationFilter != null) {
// Remove torrents that do not match the selected navigation filter // Remove torrents that do not match the selected navigation filter
for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext();) { for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext();) {
if (!currentNavigationFilter.matches(torrentIter.next(), systemSettings.treatDormantAsInactive())) if (!currentNavigationFilter.matches(torrentIter.next(), systemSettings.treatDormantAsInactive()))
torrentIter.remove(); torrentIter.remove();
} }
} }
if (filteredTorrents != null && currentTextFilter != null) { if (currentTextFilter != null) {
// Remove torrent that do not contain the text filter string // Remove torrent that do not contain the text filter string
for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext();) { for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext();) {
if (!torrentIter.next().getName().toLowerCase(Locale.getDefault()) if (!torrentIter.next().getName().toLowerCase(Locale.getDefault())

2
app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java

@ -20,7 +20,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.gui.navigation.FilterSeparatorView_; import org.transdroid.core.gui.navigation.*;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;

6
app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java

@ -35,6 +35,8 @@ import com.j256.ormlite.dao.Dao;
@EBean @EBean
public class ErrorLogSender { public class ErrorLogSender {
@Bean
protected Log log;
@Bean @Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@OrmLiteDao(helper = DatabaseHelper.class, model = ErrorLogEntry.class) @OrmLiteDao(helper = DatabaseHelper.class, model = ErrorLogEntry.class)
@ -81,11 +83,11 @@ public class ErrorLogSender {
callingActivity.startActivity(Intent.createChooser(target, callingActivity.startActivity(Intent.createChooser(target,
callingActivity.getString(R.string.pref_sendlog)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); callingActivity.getString(R.string.pref_sendlog)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
Log.i(callingActivity, "Tried to send error log, but there is no email app installed."); log.i(callingActivity, "Tried to send error log, but there is no email app installed.");
} }
} catch (SQLException e) { } catch (SQLException e) {
Log.e(callingActivity, "Cannot read the error log to build an error report to send: " + e.toString()); log.e(callingActivity, "Cannot read the error log to build an error report to send: " + e.toString());
} }
} }

46
app/src/main/java/org/transdroid/core/gui/log/Log.java

@ -16,44 +16,36 @@
*/ */
package org.transdroid.core.gui.log; package org.transdroid.core.gui.log;
import java.sql.SQLException; import com.j256.ormlite.dao.Dao;
import java.util.Date; import com.j256.ormlite.stmt.DeleteBuilder;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.EBean.Scope; import org.androidannotations.annotations.EBean.Scope;
import org.androidannotations.annotations.OrmLiteDao; import org.androidannotations.annotations.OrmLiteDao;
import org.transdroid.BuildConfig; import org.transdroid.BuildConfig;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.daemon.util.ITLogger;
import android.content.Context;
import com.j256.ormlite.dao.Dao; import java.util.Date;
import com.j256.ormlite.stmt.DeleteBuilder;
/** /**
* Application-wide logging class that registers entries in the database (for a certain time). * Application-wide logging class that registers entries in the database (for a certain time).
* @author Eric Kok * @author Eric Kok
*/ */
@EBean(scope = Scope.Singleton) @EBean(scope = Scope.Singleton)
public class Log implements ITLogger { public class Log {
public static final String LOG_NAME = "Transdroid"; public static final String LOG_NAME = "Transdroid";
private static final long MAX_LOG_AGE = 15 * 60 * 1000; // 15 minutes private static final long MAX_LOG_AGE = 15 * 60 * 1000; // 15 minutes
// Access to resources and database in local singleton instance
private Context context;
@OrmLiteDao(helper = DatabaseHelper.class, model = ErrorLogEntry.class) @OrmLiteDao(helper = DatabaseHelper.class, model = ErrorLogEntry.class)
Dao<ErrorLogEntry, Integer> errorLogDao; Dao<ErrorLogEntry, Integer> errorLogDao;
protected Log(Context context) { protected void log(Object object, int priority, String message) {
this.context = context; log(object instanceof String ? (String) object : object.getClass().getSimpleName(), priority, message);
} }
protected void log(String logName, int priority, String message) { protected void log(String logName, int priority, String message) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG) {
android.util.Log.println(priority, LOG_NAME, message); android.util.Log.println(priority, LOG_NAME, message);
}
try { try {
// Store this log message to the database // Store this log message to the database
errorLogDao.create(new ErrorLogEntry(priority, logName, message)); errorLogDao.create(new ErrorLogEntry(priority, logName, message));
@ -66,26 +58,16 @@ public class Log implements ITLogger {
} }
} }
public static void e(Context caller, String message) { public void d(Object object, String msg) {
Log_.getInstance_(caller).log(caller.getClass().toString(), android.util.Log.ERROR, message); log(object, android.util.Log.DEBUG, msg);
}
public static void i(Context caller, String message) {
Log_.getInstance_(caller).log(caller.getClass().toString(), android.util.Log.INFO, message);
}
public static void d(Context caller, String message) {
Log_.getInstance_(caller).log(caller.getClass().toString(), android.util.Log.DEBUG, message);
} }
@Override public void i(Object object, String msg) {
public void d(String self, String msg) { log(object, android.util.Log.DEBUG, msg);
Log.d(context, msg);
} }
@Override public void e(Object object, String msg) {
public void e(String self, String msg) { log(object, android.util.Log.ERROR, msg);
Log.e(context, msg);
} }
} }

7
app/src/main/java/org/transdroid/core/gui/log/LogUncaughtExceptionHandler.java

@ -33,14 +33,15 @@ public class LogUncaughtExceptionHandler implements Thread.UncaughtExceptionHand
// Write exception stack trace to the log // Write exception stack trace to the log
String prefix = "E: "; String prefix = "E: ";
Log.e(context, prefix + ex.toString()); Log_ log = Log_.getInstance_(context);
log.e(this, prefix + ex.toString());
if (ex.getCause() != null) { if (ex.getCause() != null) {
for (StackTraceElement e : ex.getCause().getStackTrace()) { for (StackTraceElement e : ex.getCause().getStackTrace()) {
Log.e(context, prefix + e.toString()); log.e(this, prefix + e.toString());
} }
} }
for (StackTraceElement e : ex.getStackTrace()) { for (StackTraceElement e : ex.getStackTrace()) {
Log.e(context, prefix + e.toString()); log.e(this, prefix + e.toString());
} }
// Rely on default Android exception handling // Rely on default Android exception handling

2
app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java

@ -20,7 +20,7 @@ import java.io.Serializable;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.Extra; import org.androidannotations.annotations.Extra;
import org.transdroid.core.gui.TorrentsActivity_; import org.transdroid.core.gui.*;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;

7
app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java

@ -49,6 +49,8 @@ public class RssfeedsActivity extends Activity {
// Settings and local data // Settings and local data
@Bean @Bean
protected Log log;
@Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
protected List<RssfeedLoader> loaders; protected List<RssfeedLoader> loaders;
@ -116,7 +118,7 @@ public class RssfeedsActivity extends Activity {
} catch (Exception e) { } catch (Exception e) {
// Catch any error that may occurred and register this failure // Catch any error that may occurred and register this failure
handleRssfeedResult(loader, null, true); handleRssfeedResult(loader, null, true);
Log.i(this, "RSS feed " + loader.getSetting().getUrl() + " error: " + e.toString()); log.i(this, "RSS feed " + loader.getSetting().getUrl() + " error: " + e.toString());
} }
} }
@ -182,8 +184,7 @@ public class RssfeedsActivity extends Activity {
if (TextUtils.isEmpty(name)) if (TextUtils.isEmpty(name))
name = loader.getSetting().getName(); name = loader.getSetting().getName();
if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(loader.getSetting().getUrl())) { if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(loader.getSetting().getUrl())) {
String host = Uri.parse(loader.getSetting().getUrl()).getHost(); name = Uri.parse(loader.getSetting().getUrl()).getHost();
name = host;
} }
RssitemsActivity_.intent(this).rssfeed(loader.getChannel()).rssfeedName(name).start(); RssitemsActivity_.intent(this).rssfeed(loader.getChannel()).rssfeedName(name).start();

2
app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java

@ -26,7 +26,7 @@ import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.gui.settings.MainSettingsActivity_; import org.transdroid.core.gui.settings.*;
import android.app.Fragment; import android.app.Fragment;
import android.view.Menu; import android.view.Menu;

151
app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java

@ -16,23 +16,6 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import java.util.ArrayList;
import java.util.List;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.ItemClick;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.navigation.SelectionManagerMode;
import org.transdroid.core.gui.search.SearchActivity_;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
@ -53,6 +36,24 @@ import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.ItemClick;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.gui.*;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.navigation.SelectionManagerMode;
import org.transdroid.core.gui.search.*;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
import java.util.ArrayList;
import java.util.List;
import de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
@ -70,56 +71,6 @@ public class RssitemsFragment extends Fragment {
// Views // Views
@ViewById(resName = "rssitems_list") @ViewById(resName = "rssitems_list")
protected ListView rssitemsList; protected ListView rssitemsList;
@Bean
protected RssitemsAdapter rssitemsAdapter;
@ViewById
protected TextView emptyText;
@AfterViews
protected void init() {
// Set up the list adapter, which allows multi-select
rssitemsList.setAdapter(rssitemsAdapter);
rssitemsList.setMultiChoiceModeListener(onItemsSelected);
update(rssfeed, hasError);
}
/**
* Update the shown RSS items in the list.
* @param channel The loaded RSS content channel object
* @param hasError True if there were errors in loading the channel, in which case an error text is shown; false
* otherwise
*/
public void update(Channel channel, boolean hasError) {
rssitemsAdapter.update(channel);
rssitemsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
if (hasError) {
emptyText.setText(R.string.rss_error);
return;
}
if (channel == null) {
emptyText.setText(R.string.rss_noselection);
return;
}
if (channel.getItems().size() == 0) {
emptyText.setText(R.string.rss_empty);
return;
}
rssitemsList.setVisibility(View.VISIBLE);
emptyText.setVisibility(View.INVISIBLE);
}
@ItemClick(resName = "rssitems_list")
protected void onItemClicked(Item item) {
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only
Intent i = TorrentsActivity_.intent(getActivity()).get();
i.setData(item.getTheLinkUri());
i.putExtra("TORRENT_TITLE", item.getTitle());
startActivity(i);
}
private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() { private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() {
SelectionManagerMode selectionManagerMode; SelectionManagerMode selectionManagerMode;
@ -143,9 +94,10 @@ public class RssitemsFragment extends Fragment {
// Get checked torrents // Get checked torrents
List<Item> checked = new ArrayList<Item>(); List<Item> checked = new ArrayList<Item>();
for (int i = 0; i < rssitemsList.getCheckedItemPositions().size(); i++) { for (int i = 0; i < rssitemsList.getCheckedItemPositions().size(); i++) {
if (rssitemsList.getCheckedItemPositions().valueAt(i)) if (rssitemsList.getCheckedItemPositions().valueAt(i)) {
checked.add(rssitemsAdapter.getItem(rssitemsList.getCheckedItemPositions().keyAt(i))); checked.add(rssitemsAdapter.getItem(rssitemsList.getCheckedItemPositions().keyAt(i)));
} }
}
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.action_addall) { if (itemId == R.id.action_addall) {
@ -169,12 +121,13 @@ public class RssitemsFragment extends Fragment {
StringBuilder names = new StringBuilder(); StringBuilder names = new StringBuilder();
for (int f = 0; f < checked.size(); f++) { for (int f = 0; f < checked.size(); f++) {
if (f != 0) if (f != 0) {
names.append("\n"); names.append("\n");
}
names.append(checked.get(f).getTitle()); names.append(checked.get(f).getTitle());
} }
ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService( ClipboardManager clipboardManager =
Context.CLIPBOARD_SERVICE); (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString())); clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
mode.finish(); mode.finish();
return true; return true;
@ -182,8 +135,9 @@ public class RssitemsFragment extends Fragment {
} else { } else {
// The other items only operate on one (the first) selected item // The other items only operate on one (the first) selected item
if (checked.size() < 1) if (checked.size() < 1) {
return false; return false;
}
final Item first = checked.get(0); final Item first = checked.get(0);
if (itemId == R.id.action_showdetails) { if (itemId == R.id.action_showdetails) {
// Show a dialog box with the RSS item description text // Show a dialog box with the RSS item description text
@ -191,7 +145,9 @@ public class RssitemsFragment extends Fragment {
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity()).setMessage(first.getDescription()) return new AlertDialog.Builder(getActivity()).setMessage(first.getDescription())
.setPositiveButton(R.string.action_close, null).create(); .setPositiveButton(R.string.action_close, null).create();
}; }
;
}.show(getFragmentManager(), "RssItemDescription"); }.show(getFragmentManager(), "RssItemDescription");
} else if (itemId == R.id.action_openwebsite) { } else if (itemId == R.id.action_openwebsite) {
// Open the browser to show the website contained in the item's link tag // Open the browser to show the website contained in the item's link tag
@ -227,5 +183,54 @@ public class RssitemsFragment extends Fragment {
} }
}; };
@Bean
protected RssitemsAdapter rssitemsAdapter;
@ViewById
protected TextView emptyText;
@AfterViews
protected void init() {
// Set up the list adapter, which allows multi-select
rssitemsList.setAdapter(rssitemsAdapter);
rssitemsList.setMultiChoiceModeListener(onItemsSelected);
update(rssfeed, hasError);
}
/**
* Update the shown RSS items in the list.
* @param channel The loaded RSS content channel object
* @param hasError True if there were errors in loading the channel, in which case an error text is shown; false
* otherwise
*/
public void update(Channel channel, boolean hasError) {
rssitemsAdapter.update(channel);
rssitemsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
if (hasError) {
emptyText.setText(R.string.rss_error);
return;
}
if (channel == null) {
emptyText.setText(R.string.rss_noselection);
return;
}
if (channel.getItems().size() == 0) {
emptyText.setText(R.string.rss_empty);
return;
}
rssitemsList.setVisibility(View.VISIBLE);
emptyText.setVisibility(View.INVISIBLE);
}
@ItemClick(resName = "rssitems_list")
protected void onItemClicked(Item item) {
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only
Intent i = TorrentsActivity_.intent(getActivity()).get();
i.setData(item.getTheLinkUri());
i.putExtra("TORRENT_TITLE", item.getTitle());
startActivity(i);
}
} }

90
app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java

@ -16,25 +16,6 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import java.util.List;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.SystemService;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.app.search.SearchHelper;
import org.transdroid.core.app.search.SearchSite;
import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.app.settings.WebsearchSetting;
import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.NavigationHelper;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener; import android.app.ActionBar.OnNavigationListener;
@ -54,6 +35,23 @@ import android.widget.ListView;
import android.widget.SearchView; import android.widget.SearchView;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.SystemService;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.app.search.SearchHelper;
import org.transdroid.core.app.search.SearchSite;
import org.transdroid.core.app.settings.*;
import org.transdroid.core.gui.*;
import org.transdroid.core.gui.navigation.NavigationHelper;
import java.util.List;
/** /**
* An activity that shows search results to the user (after a query was supplied by the standard Android search manager) * An activity that shows search results to the user (after a query was supplied by the standard Android search manager)
* and either shows the list of search sites on the left (e.g. on tablets) or allows switching between search sites via * and either shows the list of search sites on the left (e.g. on tablets) or allows switching between search sites via
@ -79,11 +77,19 @@ public class SearchActivity extends Activity implements OnNavigationListener {
@SystemService @SystemService
protected SearchManager searchManager; protected SearchManager searchManager;
private MenuItem searchMenu = null; private MenuItem searchMenu = null;
private SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchHistoryProvider.AUTHORITY, private SearchRecentSuggestions suggestions =
SearchHistoryProvider.MODE); new SearchRecentSuggestions(this, SearchHistoryProvider.AUTHORITY, SearchHistoryProvider.MODE);
private List<SearchSetting> searchSites; private List<SearchSetting> searchSites;
private SearchSetting lastUsedSite; private SearchSetting lastUsedSite;
private OnItemClickListener onSearchSiteClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lastUsedSite = searchSites.get(position);
refreshSearch();
}
};
private String lastUsedQuery; private String lastUsedQuery;
@Override @Override
@ -129,17 +135,19 @@ public class SearchActivity extends Activity implements OnNavigationListener {
searchsitesList.setAdapter(searchSitesAdapter); searchsitesList.setAdapter(searchSitesAdapter);
searchsitesList.setOnItemClickListener(onSearchSiteClicked); searchsitesList.setOnItemClickListener(onSearchSiteClicked);
// Select the last used site; this also starts the search! // Select the last used site; this also starts the search!
if (lastUsedPosition >= 0) if (lastUsedPosition >= 0) {
searchsitesList.setItemChecked(lastUsedPosition, true); searchsitesList.setItemChecked(lastUsedPosition, true);
}
} else { } else {
// Use the action bar spinner to select sites // Use the action bar spinner to select sites
getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
getActionBar().setDisplayShowTitleEnabled(false); getActionBar().setDisplayShowTitleEnabled(false);
getActionBar().setListNavigationCallbacks(new SearchSettingsDropDownAdapter(this, searchSites), this); getActionBar().setListNavigationCallbacks(new SearchSettingsDropDownAdapter(this, searchSites), this);
// Select the last used site; this also starts the search! // Select the last used site; this also starts the search!
if (lastUsedPosition >= 0) if (lastUsedPosition >= 0) {
getActionBar().setSelectedNavigationItem(lastUsedPosition); getActionBar().setSelectedNavigationItem(lastUsedPosition);
} }
}
} }
@ -166,12 +174,14 @@ public class SearchActivity extends Activity implements OnNavigationListener {
menu.findItem(R.id.action_search).setVisible(searchInstalled); menu.findItem(R.id.action_search).setVisible(searchInstalled);
menu.findItem(R.id.action_refresh).setVisible(searchInstalled); menu.findItem(R.id.action_refresh).setVisible(searchInstalled);
menu.findItem(R.id.action_downloadsearch).setVisible(!searchInstalled); menu.findItem(R.id.action_downloadsearch).setVisible(!searchInstalled);
if (searchsitesList != null) if (searchsitesList != null) {
searchsitesList.setVisibility(searchInstalled ? View.VISIBLE : View.GONE); searchsitesList.setVisibility(searchInstalled ? View.VISIBLE : View.GONE);
if (searchInstalled) }
if (searchInstalled) {
getFragmentManager().beginTransaction().show(fragmentResults).commit(); getFragmentManager().beginTransaction().show(fragmentResults).commit();
else } else {
getFragmentManager().beginTransaction().hide(fragmentResults).commit(); getFragmentManager().beginTransaction().hide(fragmentResults).commit();
}
installmoduleText.setVisibility(searchInstalled ? View.GONE : View.VISIBLE); installmoduleText.setVisibility(searchInstalled ? View.GONE : View.VISIBLE);
return true; return true;
@ -188,15 +198,13 @@ public class SearchActivity extends Activity implements OnNavigationListener {
lastUsedQuery = parseQuery(intent); lastUsedQuery = parseQuery(intent);
// Is this actually a full HTTP URL? Then redirect this request to add the URL directly // Is this actually a full HTTP URL? Then redirect this request to add the URL directly
if (lastUsedQuery != null if (lastUsedQuery != null && (lastUsedQuery.startsWith("http") || lastUsedQuery.startsWith("https") ||
&& (lastUsedQuery.startsWith("http") || lastUsedQuery.startsWith("https") lastUsedQuery.startsWith("magnet") || lastUsedQuery.startsWith("file"))) {
|| lastUsedQuery.startsWith("magnet") || lastUsedQuery.startsWith("file"))) {
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only // Don't broadcast this intent; we can safely assume this is intended for Transdroid only
Intent i = TorrentsActivity_.intent(this).get(); Intent i = TorrentsActivity_.intent(this).get();
i.setData(Uri.parse(lastUsedQuery)); i.setData(Uri.parse(lastUsedQuery));
startActivity(i); startActivity(i);
finish(); finish();
return;
} }
} }
@ -215,15 +223,6 @@ public class SearchActivity extends Activity implements OnNavigationListener {
TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
} }
private OnItemClickListener onSearchSiteClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lastUsedSite = searchSites.get(position);
refreshSearch();
}
};
@Override @Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) { public boolean onNavigationItemSelected(int itemPosition, long itemId) {
lastUsedSite = searchSites.get(itemPosition); lastUsedSite = searchSites.get(itemPosition);
@ -239,11 +238,10 @@ public class SearchActivity extends Activity implements OnNavigationListener {
String query = null; String query = null;
if (intent.getAction().equals(Intent.ACTION_SEARCH)) { if (intent.getAction().equals(Intent.ACTION_SEARCH)) {
query = intent.getStringExtra(SearchManager.QUERY); query = intent.getStringExtra(SearchManager.QUERY).trim();
} else if (intent.getAction().equals(Intent.ACTION_SEND)) { } else if (intent.getAction().equals(Intent.ACTION_SEND)) {
query = SendIntentHelper.cleanUpText(intent); query = SendIntentHelper.cleanUpText(intent).trim();
} }
query = query.trim();
if (query != null && query.length() > 0) { if (query != null && query.length() > 0) {
// Remember this search query to later show as a suggestion // Remember this search query to later show as a suggestion
@ -267,7 +265,8 @@ public class SearchActivity extends Activity implements OnNavigationListener {
// Start a browser page directly to the requested search results // Start a browser page directly to the requested search results
WebsearchSetting websearch = (WebsearchSetting) lastUsedSite; WebsearchSetting websearch = (WebsearchSetting) lastUsedSite;
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(websearch.getBaseUrl().replace("%s", lastUsedQuery)))); startActivity(
new Intent(Intent.ACTION_VIEW, Uri.parse(websearch.getBaseUrl().replace("%s", lastUsedQuery))));
finish(); finish();
} else if (lastUsedSite instanceof SearchSite) { } else if (lastUsedSite instanceof SearchSite) {
@ -275,9 +274,8 @@ public class SearchActivity extends Activity implements OnNavigationListener {
// Save the search site currently used to search for future usage // Save the search site currently used to search for future usage
applicationSettings.setLastUsedSearchSite(lastUsedSite); applicationSettings.setLastUsedSearchSite(lastUsedSite);
// Update the activity title (only shown on large devices) // Update the activity title (only shown on large devices)
getActionBar().setTitle( getActionBar().setTitle(NavigationHelper.buildCondensedFontString(
NavigationHelper.buildCondensedFontString(getString(R.string.search_queryonsite, lastUsedQuery, getString(R.string.search_queryonsite, lastUsedQuery, lastUsedSite.getName())));
lastUsedSite.getName())));
// Ask the results fragment to start a search for the specified query // Ask the results fragment to start a search for the specified query
fragmentResults.startSearch(lastUsedQuery, (SearchSite) lastUsedSite); fragmentResults.startSearch(lastUsedQuery, (SearchSite) lastUsedSite);

187
app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java

@ -16,8 +16,18 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import java.util.ArrayList; import android.app.Fragment;
import java.util.List; import android.content.Intent;
import android.net.Uri;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Background;
@ -32,24 +42,13 @@ import org.transdroid.core.app.search.SearchHelper;
import org.transdroid.core.app.search.SearchHelper.SearchSortOrder; import org.transdroid.core.app.search.SearchHelper.SearchSortOrder;
import org.transdroid.core.app.search.SearchResult; import org.transdroid.core.app.search.SearchResult;
import org.transdroid.core.app.search.SearchSite; import org.transdroid.core.app.search.SearchSite;
import org.transdroid.core.app.settings.SystemSettings_; import org.transdroid.core.app.settings.*;
import org.transdroid.core.gui.TorrentsActivity_; import org.transdroid.core.gui.*;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.*;
import org.transdroid.core.gui.navigation.NavigationHelper_;
import org.transdroid.core.gui.navigation.SelectionManagerMode; import java.util.ArrayList;
import java.util.List;
import android.app.Fragment;
import android.content.Intent;
import android.net.Uri;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
@ -69,76 +68,6 @@ public class SearchResultsFragment extends Fragment {
// Views // Views
@ViewById(resName = "searchresults_list") @ViewById(resName = "searchresults_list")
protected ListView resultsList; protected ListView resultsList;
@Bean
protected SearchResultsAdapter resultsAdapter;
@ViewById
protected TextView emptyText;
@ViewById
protected ProgressBar loadingProgress;
@AfterViews
protected void init() {
// On large screens where this fragment is shown next to the sites list; we show a continues grey vertical line
// to separate the lists visually
if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) {
if (SystemSettings_.getInstance_(getActivity()).useDarkTheme()) {
resultsList.setBackgroundResource(R.drawable.details_list_background_dark);
} else {
resultsList.setBackgroundResource(R.drawable.details_list_background_light);
}
}
// Set up the list adapter, which allows multi-select
resultsList.setAdapter(resultsAdapter);
resultsList.setMultiChoiceModeListener(onItemsSelected);
if (results != null)
showResults();
}
public void startSearch(String query, SearchSite site) {
loadingProgress.setVisibility(View.VISIBLE);
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.GONE);
performSearch(query, site);
}
@Background
protected void performSearch(String query, SearchSite site) {
results = searchHelper.search(query, site, SearchSortOrder.BySeeders);
resultsSource = site.isPrivate()? site.getKey(): null;
showResults();
}
@UiThread
protected void showResults() {
loadingProgress.setVisibility(View.GONE);
if (results == null || results.size() == 0) {
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
return;
}
resultsAdapter.update(results);
resultsList.setVisibility(View.VISIBLE);
emptyText.setVisibility(View.GONE);
}
@ItemClick(resName = "searchresults_list")
protected void onItemClicked(SearchResult item) {
if (item.getTorrentUrl() == null) {
Crouton.showText(getActivity(), R.string.error_notorrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
return;
}
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only
Intent i = TorrentsActivity_.intent(getActivity()).get();
i.setData(Uri.parse(item.getTorrentUrl()));
i.putExtra("TORRENT_TITLE", item.getName());
if (resultsSource != null)
i.putExtra("PRIVATE_SOURCE", resultsSource);
startActivity(i);
}
private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() { private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() {
SelectionManagerMode selectionManagerMode; SelectionManagerMode selectionManagerMode;
@ -162,9 +91,10 @@ public class SearchResultsFragment extends Fragment {
// Get checked torrents // Get checked torrents
List<SearchResult> checked = new ArrayList<SearchResult>(); List<SearchResult> checked = new ArrayList<SearchResult>();
for (int i = 0; i < resultsList.getCheckedItemPositions().size(); i++) { for (int i = 0; i < resultsList.getCheckedItemPositions().size(); i++) {
if (resultsList.getCheckedItemPositions().valueAt(i)) if (resultsList.getCheckedItemPositions().valueAt(i)) {
checked.add(resultsAdapter.getItem(resultsList.getCheckedItemPositions().keyAt(i))); checked.add(resultsAdapter.getItem(resultsList.getCheckedItemPositions().keyAt(i)));
} }
}
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.action_addall) { if (itemId == R.id.action_addall) {
@ -179,17 +109,19 @@ public class SearchResultsFragment extends Fragment {
} }
intent.putExtra("TORRENT_URLS", urls); intent.putExtra("TORRENT_URLS", urls);
intent.putExtra("TORRENT_TITLES", titles); intent.putExtra("TORRENT_TITLES", titles);
if (resultsSource != null) if (resultsSource != null) {
intent.putExtra("PRIVATE_SOURCE", resultsSource); intent.putExtra("PRIVATE_SOURCE", resultsSource);
}
startActivity(intent); startActivity(intent);
mode.finish(); mode.finish();
return true; return true;
} else if (itemId == R.id.action_showdetails) { } else if (itemId == R.id.action_showdetails) {
SearchResult first = checked.get(0); SearchResult first = checked.get(0);
// Open the torrent's web page in the browser // Open the torrent's web page in the browser
if (checked.size() > 1) if (checked.size() > 1) {
Toast.makeText(getActivity(), getString(R.string.search_openingdetails, first.getName()), Toast.makeText(getActivity(), getString(R.string.search_openingdetails, first.getName()),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
}
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getDetailsUrl()))); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getDetailsUrl())));
return true; return true;
} else { } else {
@ -208,5 +140,76 @@ public class SearchResultsFragment extends Fragment {
} }
}; };
@Bean
protected SearchResultsAdapter resultsAdapter;
@ViewById
protected TextView emptyText;
@ViewById
protected ProgressBar loadingProgress;
@AfterViews
protected void init() {
// On large screens where this fragment is shown next to the sites list; we show a continues grey vertical line
// to separate the lists visually
if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) {
if (SystemSettings_.getInstance_(getActivity()).useDarkTheme()) {
resultsList.setBackgroundResource(R.drawable.details_list_background_dark);
} else {
resultsList.setBackgroundResource(R.drawable.details_list_background_light);
}
}
// Set up the list adapter, which allows multi-select
resultsList.setAdapter(resultsAdapter);
resultsList.setMultiChoiceModeListener(onItemsSelected);
if (results != null) {
showResults();
}
}
public void startSearch(String query, SearchSite site) {
loadingProgress.setVisibility(View.VISIBLE);
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.GONE);
performSearch(query, site);
}
@Background
protected void performSearch(String query, SearchSite site) {
results = searchHelper.search(query, site, SearchSortOrder.BySeeders);
resultsSource = site.isPrivate() ? site.getKey() : null;
showResults();
}
@UiThread
protected void showResults() {
loadingProgress.setVisibility(View.GONE);
if (results == null || results.size() == 0) {
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
return;
}
resultsAdapter.update(results);
resultsList.setVisibility(View.VISIBLE);
emptyText.setVisibility(View.GONE);
}
@ItemClick(resName = "searchresults_list")
protected void onItemClicked(SearchResult item) {
if (item.getTorrentUrl() == null) {
Crouton.showText(getActivity(), R.string.error_notorrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
return;
}
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only
Intent i = TorrentsActivity_.intent(getActivity()).get();
i.setData(Uri.parse(item.getTorrentUrl()));
i.putExtra("TORRENT_TITLE", item.getName());
if (resultsSource != null) {
i.putExtra("PRIVATE_SOURCE", resultsSource);
}
startActivity(i);
}
} }

251
app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java

@ -16,8 +16,20 @@
*/ */
package org.transdroid.core.gui.settings; package org.transdroid.core.gui.settings;
import java.util.ArrayList; import android.annotation.TargetApi;
import java.util.List; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.view.View;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
@ -29,7 +41,7 @@ import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.RssfeedSetting; import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.app.settings.WebsearchSetting; import org.transdroid.core.app.settings.WebsearchSetting;
import org.transdroid.core.gui.TorrentsActivity_; import org.transdroid.core.gui.*;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.settings.OverflowPreference.OnOverflowClicked; import org.transdroid.core.gui.settings.OverflowPreference.OnOverflowClicked;
import org.transdroid.core.gui.settings.RssfeedPreference.OnRssfeedClickedListener; import org.transdroid.core.gui.settings.RssfeedPreference.OnRssfeedClickedListener;
@ -39,20 +51,8 @@ import org.transdroid.core.seedbox.SeedboxPreference;
import org.transdroid.core.seedbox.SeedboxPreference.OnSeedboxClickedListener; import org.transdroid.core.seedbox.SeedboxPreference.OnSeedboxClickedListener;
import org.transdroid.core.seedbox.SeedboxProvider; import org.transdroid.core.seedbox.SeedboxProvider;
import android.annotation.TargetApi; import java.util.ArrayList;
import android.app.AlertDialog; import java.util.List;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.view.View;
/** /**
* The main activity that provides access to all application settings. It shows the configured serves, web search sites * The main activity that provides access to all application settings. It shows the configured serves, web search sites
@ -63,7 +63,13 @@ import android.view.View;
public class MainSettingsActivity extends PreferenceActivity { public class MainSettingsActivity extends PreferenceActivity {
protected static final int DIALOG_ADDSEEDBOX = 0; protected static final int DIALOG_ADDSEEDBOX = 0;
private OnOverflowClicked onOverflowClicked = new OnOverflowClicked() {
@SuppressWarnings("deprecation")
@Override
public void onOverflowClicked(View overflowButton) {
showDialog(DIALOG_ADDSEEDBOX);
}
};
@Bean @Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@Bean @Bean
@ -71,6 +77,83 @@ public class MainSettingsActivity extends PreferenceActivity {
@Bean @Bean
protected SearchHelper searchHelper; protected SearchHelper searchHelper;
protected SharedPreferences prefs; protected SharedPreferences prefs;
private OnPreferenceClickListener onAddServer = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
ServerSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onAddWebsearch = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
WebsearchSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onAddRssfeed = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
RssfeedSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onBackgroundSettings = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
NotificationSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onSystemSettings = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
SystemSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onHelpSettings = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
HelpSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnServerClickedListener onServerClicked = new OnServerClickedListener() {
@Override
public void onServerClicked(ServerSetting serverSetting) {
ServerSettingsActivity_.intent(MainSettingsActivity.this).key(serverSetting.getOrder()).start();
}
};
private OnSeedboxClickedListener onSeedboxClicked = new OnSeedboxClickedListener() {
@Override
public void onSeedboxClicked(ServerSetting serverSetting, SeedboxProvider provider, int seedboxOffset) {
// NOTE: The seedboxOffset is the seedbox type-unique order that we need to supply uin the Extras bundle to
// edit this specific seedbox
startActivity(provider.getSettings().getSettingsActivityIntent(MainSettingsActivity.this)
.putExtra("key", seedboxOffset));
}
};
private OnWebsearchClickedListener onWebsearchClicked = new OnWebsearchClickedListener() {
@Override
public void onWebsearchClicked(WebsearchSetting websearchSetting) {
WebsearchSettingsActivity_.intent(MainSettingsActivity.this).key(websearchSetting.getOrder()).start();
}
};
private OnRssfeedClickedListener onRssfeedClicked = new OnRssfeedClickedListener() {
@Override
public void onRssfeedClicked(RssfeedSetting rssfeedSetting) {
RssfeedSettingsActivity_.intent(MainSettingsActivity.this).key(rssfeedSetting.getOrder()).start();
}
};
private OnClickListener onAddSeedbox = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Start the configuration activity for this specific chosen seedbox
startActivity(
SeedboxProvider.values()[which].getSettings().getSettingsActivityIntent(MainSettingsActivity.this));
}
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -86,8 +169,9 @@ public class MainSettingsActivity extends PreferenceActivity {
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
prefs = getPreferenceManager().getSharedPreferences(); prefs = getPreferenceManager().getSharedPreferences();
if (getPreferenceScreen() != null) if (getPreferenceScreen() != null) {
getPreferenceScreen().removeAll(); getPreferenceScreen().removeAll();
}
boolean enableSearchUi = navigationHelper.enableSearchUi(); boolean enableSearchUi = navigationHelper.enableSearchUi();
boolean enableRssUi = navigationHelper.enableRssUi(); boolean enableRssUi = navigationHelper.enableRssUi();
@ -96,14 +180,17 @@ public class MainSettingsActivity extends PreferenceActivity {
addPreferencesFromResource(R.xml.pref_main); addPreferencesFromResource(R.xml.pref_main);
OverflowPreference addServerPrefernce = (OverflowPreference) findPreference("header_addserver"); OverflowPreference addServerPrefernce = (OverflowPreference) findPreference("header_addserver");
addServerPrefernce.setOnPreferenceClickListener(onAddServer); addServerPrefernce.setOnPreferenceClickListener(onAddServer);
if (navigationHelper.enableSeedboxes()) if (navigationHelper.enableSeedboxes()) {
addServerPrefernce.setOnOverflowClickedListener(onOverflowClicked); addServerPrefernce.setOnOverflowClickedListener(onOverflowClicked);
else } else {
addServerPrefernce.hideOverflowButton(); addServerPrefernce.hideOverflowButton();
if (enableSearchUi) }
if (enableSearchUi) {
findPreference("header_addwebsearch").setOnPreferenceClickListener(onAddWebsearch); findPreference("header_addwebsearch").setOnPreferenceClickListener(onAddWebsearch);
if (enableRssUi) }
if (enableRssUi) {
findPreference("header_addrssfeed").setOnPreferenceClickListener(onAddRssfeed); findPreference("header_addrssfeed").setOnPreferenceClickListener(onAddRssfeed);
}
findPreference("header_background").setOnPreferenceClickListener(onBackgroundSettings); findPreference("header_background").setOnPreferenceClickListener(onBackgroundSettings);
findPreference("header_system").setOnPreferenceClickListener(onSystemSettings); findPreference("header_system").setOnPreferenceClickListener(onSystemSettings);
findPreference("header_help").setOnPreferenceClickListener(onHelpSettings); findPreference("header_help").setOnPreferenceClickListener(onHelpSettings);
@ -119,9 +206,8 @@ public class MainSettingsActivity extends PreferenceActivity {
// Add existing servers // Add existing servers
List<ServerSetting> servers = applicationSettings.getNormalServerSettings(); List<ServerSetting> servers = applicationSettings.getNormalServerSettings();
for (ServerSetting serverSetting : servers) { for (ServerSetting serverSetting : servers) {
getPreferenceScreen().addPreference( getPreferenceScreen().addPreference(new ServerPreference(this).setServerSetting(serverSetting)
new ServerPreference(this).setServerSetting(serverSetting).setOnServerClickedListener( .setOnServerClickedListener(onServerClicked));
onServerClicked));
if (serverSetting.getUniqueIdentifier() != null) { if (serverSetting.getUniqueIdentifier() != null) {
serverCodes.add(Integer.toString(serverSetting.getOrder())); serverCodes.add(Integer.toString(serverSetting.getOrder()));
serverNames.add(serverSetting.getName()); serverNames.add(serverSetting.getName());
@ -156,9 +242,8 @@ public class MainSettingsActivity extends PreferenceActivity {
} else { } else {
List<RssfeedSetting> rssfeeds = applicationSettings.getRssfeedSettings(); List<RssfeedSetting> rssfeeds = applicationSettings.getRssfeedSettings();
for (RssfeedSetting rssfeedSetting : rssfeeds) { for (RssfeedSetting rssfeedSetting : rssfeeds) {
getPreferenceScreen().addPreference( getPreferenceScreen().addPreference(new RssfeedPreference(this).setRssfeedSetting(rssfeedSetting)
new RssfeedPreference(this).setRssfeedSetting(rssfeedSetting).setOnRssfeedClickedListener( .setOnRssfeedClickedListener(onRssfeedClicked));
onRssfeedClicked));
} }
} }
@ -171,17 +256,17 @@ public class MainSettingsActivity extends PreferenceActivity {
// Add existing websearch sites // Add existing websearch sites
List<WebsearchSetting> websearches = applicationSettings.getWebsearchSettings(); List<WebsearchSetting> websearches = applicationSettings.getWebsearchSettings();
for (WebsearchSetting websearchSetting : websearches) { for (WebsearchSetting websearchSetting : websearches) {
getPreferenceScreen().addPreference( getPreferenceScreen().addPreference(new WebsearchPreference(this).setWebsearchSetting(websearchSetting)
new WebsearchPreference(this).setWebsearchSetting(websearchSetting).setOnWebsearchClickedListener( .setOnWebsearchClickedListener(onWebsearchClicked));
onWebsearchClicked));
} }
// Construct list of all available search sites, in-app and web // Construct list of all available search sites, in-app and web
ListPreference setSite = (ListPreference) findPreference("header_setsearchsite"); ListPreference setSite = (ListPreference) findPreference("header_setsearchsite");
// Retrieve the available in-app search sites (using the Torrent Search package) // Retrieve the available in-app search sites (using the Torrent Search package)
List<SearchSite> searchsites = searchHelper.getAvailableSites(); List<SearchSite> searchsites = searchHelper.getAvailableSites();
if (searchsites == null) if (searchsites == null) {
searchsites = new ArrayList<SearchSite>(); searchsites = new ArrayList<SearchSite>();
}
List<String> siteNames = new ArrayList<String>(websearches.size() + searchsites.size()); List<String> siteNames = new ArrayList<String>(websearches.size() + searchsites.size());
List<String> siteValues = new ArrayList<String>(websearches.size() + searchsites.size()); List<String> siteValues = new ArrayList<String>(websearches.size() + searchsites.size());
for (SearchSite searchSite : searchsites) { for (SearchSite searchSite : searchsites) {
@ -210,114 +295,18 @@ public class MainSettingsActivity extends PreferenceActivity {
super.onBuildHeaders(target); super.onBuildHeaders(target);
} }
private OnPreferenceClickListener onAddServer = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
ServerSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnOverflowClicked onOverflowClicked = new OnOverflowClicked() {
@SuppressWarnings("deprecation")
@Override
public void onOverflowClicked(View overflowButton) {
showDialog(DIALOG_ADDSEEDBOX);
}
};
private OnPreferenceClickListener onAddWebsearch = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
WebsearchSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onAddRssfeed = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
RssfeedSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onBackgroundSettings = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
NotificationSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onSystemSettings = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
SystemSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnPreferenceClickListener onHelpSettings = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
HelpSettingsActivity_.intent(MainSettingsActivity.this).start();
return true;
}
};
private OnServerClickedListener onServerClicked = new OnServerClickedListener() {
@Override
public void onServerClicked(ServerSetting serverSetting) {
ServerSettingsActivity_.intent(MainSettingsActivity.this).key(serverSetting.getOrder()).start();
}
};
private OnSeedboxClickedListener onSeedboxClicked = new OnSeedboxClickedListener() {
@Override
public void onSeedboxClicked(ServerSetting serverSetting, SeedboxProvider provider, int seedboxOffset) {
// NOTE: The seedboxOffset is the seedbox type-unique order that we need to supply uin the Extras bundle to
// edit this specific seedbox
startActivity(provider.getSettings().getSettingsActivityIntent(MainSettingsActivity.this)
.putExtra("key", seedboxOffset));
}
};
private OnWebsearchClickedListener onWebsearchClicked = new OnWebsearchClickedListener() {
@Override
public void onWebsearchClicked(WebsearchSetting websearchSetting) {
WebsearchSettingsActivity_.intent(MainSettingsActivity.this).key(websearchSetting.getOrder()).start();
}
};
private OnRssfeedClickedListener onRssfeedClicked = new OnRssfeedClickedListener() {
@Override
public void onRssfeedClicked(RssfeedSetting rssfeedSetting) {
RssfeedSettingsActivity_.intent(MainSettingsActivity.this).key(rssfeedSetting.getOrder()).start();
}
};
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
switch (id) { switch (id) {
case DIALOG_ADDSEEDBOX: case DIALOG_ADDSEEDBOX:
// Open dialog to pick one of the supported seedbox providers // Open dialog to pick one of the supported seedbox providers
String[] seedboxes = new String[SeedboxProvider.values().length]; String[] seedboxes = new String[SeedboxProvider.values().length];
for (int i = 0; i < seedboxes.length; i++) { for (int i = 0; i < seedboxes.length; i++) {
seedboxes[i] = getString(R.string.pref_seedbox_addseedbox, SeedboxProvider.values()[i].getSettings() seedboxes[i] = getString(R.string.pref_seedbox_addseedbox,
.getName()); SeedboxProvider.values()[i].getSettings().getName());
} }
return new AlertDialog.Builder(this).setItems(seedboxes, onAddSeedbox).create(); return new AlertDialog.Builder(this).setItems(seedboxes, onAddSeedbox).create();
} }
return null; return null;
} }
private OnClickListener onAddSeedbox = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Start the configuration activity for this specific chosen seedbox
startActivity(SeedboxProvider.values()[which].getSettings().getSettingsActivityIntent(
MainSettingsActivity.this));
}
};
} }

4
app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java

@ -119,8 +119,8 @@ public class ServerSettingsActivity extends KeyBoundPreferencesActivity {
extraPass.setEnabled(Daemon.supportsExtraPassword(daemonType)); extraPass.setEnabled(Daemon.supportsExtraPassword(daemonType));
extraPass.setTitle(getString(daemonType == Daemon.Deluge ? R.string.pref_extrapassword : R.string.pref_secret)); extraPass.setTitle(getString(daemonType == Daemon.Deluge ? R.string.pref_extrapassword : R.string.pref_secret));
extraPass.setDialogTitle(extraPass.getTitle()); extraPass.setDialogTitle(extraPass.getTitle());
folder.setEnabled(daemonType == null ? false : Daemon.supportsCustomFolder(daemonType)); folder.setEnabled(daemonType != null && Daemon.supportsCustomFolder(daemonType));
downloadDir.setEnabled(daemonType == null ? false : Daemon.needsManualPathSpecified(daemonType)); downloadDir.setEnabled(daemonType != null && Daemon.needsManualPathSpecified(daemonType));
// sslTrustKey.setEnabled(sslValue && !sslTAValue); // sslTrustKey.setEnabled(sslValue && !sslTAValue);
// Adjust title texts accordingly // Adjust title texts accordingly

8
app/src/main/java/org/transdroid/core/seedbox/XirvikSharedSettingsActivity.java

@ -18,6 +18,7 @@ package org.transdroid.core.seedbox;
import java.io.InputStream; import java.io.InputStream;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenu;
@ -51,6 +52,9 @@ import de.keyboardsurfer.android.widget.crouton.Crouton;
@OptionsMenu(resName = "activity_deleteableprefs") @OptionsMenu(resName = "activity_deleteableprefs")
public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity { public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity {
@Bean
protected Log log;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -99,7 +103,7 @@ public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity {
} catch (Exception e) { } catch (Exception e) {
Log.d(XirvikSharedSettingsActivity.this, log.d(XirvikSharedSettingsActivity.this,
"Could not retrieve the Xirvik shared seedbox RPC mount point setting: " + e.toString()); "Could not retrieve the Xirvik shared seedbox RPC mount point setting: " + e.toString());
return null; return null;
@ -126,7 +130,7 @@ public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity {
edit.putString("seedbox_xirvikshared_rpc_" + key, result); edit.putString("seedbox_xirvikshared_rpc_" + key, result);
pref.setSummary(result); pref.setSummary(result);
} }
edit.commit(); edit.apply();
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)

15
app/src/main/java/org/transdroid/core/service/AppUpdateService.java

@ -55,6 +55,8 @@ public class AppUpdateService extends IntentService {
private static final String DOWNLOAD_URL_APP = "http://www.transdroid.org/latest"; private static final String DOWNLOAD_URL_APP = "http://www.transdroid.org/latest";
private static final String DOWNLOAD_URL_SEARCH = "http://www.transdroid.org/latest-search"; private static final String DOWNLOAD_URL_SEARCH = "http://www.transdroid.org/latest-search";
@Bean
protected Log log;
@Bean @Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@Bean @Bean
@ -78,7 +80,7 @@ public class AppUpdateService extends IntentService {
return; return;
if (!connectivityHelper.shouldPerformBackgroundActions() || !systemSettings.checkForUpdates()) { if (!connectivityHelper.shouldPerformBackgroundActions() || !systemSettings.checkForUpdates()) {
Log.d(this, "Skip the app update service, as background data is disabled, the service is explicitly " + log.d(this, "Skip the app update service, as background data is disabled, the service is explicitly " +
"disabled or we are not connected."); "disabled or we are not connected.");
return; return;
} }
@ -87,7 +89,7 @@ public class AppUpdateService extends IntentService {
Calendar lastDay = Calendar.getInstance(); Calendar lastDay = Calendar.getInstance();
lastDay.add(Calendar.DAY_OF_MONTH, -1); lastDay.add(Calendar.DAY_OF_MONTH, -1);
if (lastChecked != null && lastChecked.after(lastDay.getTime())) { if (lastChecked != null && lastChecked.after(lastDay.getTime())) {
Log.d(this, "Skip the update service, as we already checked the last 24 hours (or to be exact at " log.d(this, "Skip the update service, as we already checked the last 24 hours (or to be exact at "
+ lastChecked.toString() + ")."); + lastChecked.toString() + ").");
return; return;
} }
@ -106,7 +108,7 @@ public class AppUpdateService extends IntentService {
// New version of the app? // New version of the app?
try { try {
PackageInfo appPackage = getPackageManager().getPackageInfo(getPackageName(), 0); PackageInfo appPackage = getPackageManager().getPackageInfo(getPackageName(), 0);
Log.d(this, "Local Transdroid is at " + appPackage.versionCode + " and the reported latest version is " log.d(this, "Local Transdroid is at " + appPackage.versionCode + " and the reported latest version is "
+ appVersion); + appVersion);
if (appPackage.versionCode < appVersion) { if (appPackage.versionCode < appVersion) {
// New version available! Notify the user. // New version available! Notify the user.
@ -122,7 +124,7 @@ public class AppUpdateService extends IntentService {
// New version of the search module? // New version of the search module?
try { try {
PackageInfo searchPackage = getPackageManager().getPackageInfo("org.transdroid.search", 0); PackageInfo searchPackage = getPackageManager().getPackageInfo("org.transdroid.search", 0);
Log.d(this, "Local Transdroid Seach is at " + searchPackage.versionCode log.d(this, "Local Transdroid Seach is at " + searchPackage.versionCode
+ " and the reported latest version is " + searchVersion); + " and the reported latest version is " + searchVersion);
if (searchPackage.versionCode < searchVersion) { if (searchPackage.versionCode < searchVersion) {
// New version available! Notify the user. // New version available! Notify the user.
@ -142,7 +144,7 @@ public class AppUpdateService extends IntentService {
} catch (Exception e) { } catch (Exception e) {
// Cannot check right now for some reason; log and ignore // Cannot check right now for some reason; log and ignore
Log.d(this, "Cannot retrieve latest app or search module version code from the site: " + e.toString()); log.d(this, "Cannot retrieve latest app or search module version code from the site: " + e.toString());
} }
} }
@ -156,8 +158,7 @@ public class AppUpdateService extends IntentService {
* @throws ClientProtocolException Thrown when the provided URL is invalid * @throws ClientProtocolException Thrown when the provided URL is invalid
* @throws IOException Thrown when the last version information could not be retrieved * @throws IOException Thrown when the last version information could not be retrieved
*/ */
private String[] retrieveLatestVersion(AbstractHttpClient httpclient, String url) throws ClientProtocolException, private String[] retrieveLatestVersion(AbstractHttpClient httpclient, String url) throws IOException {
IOException {
HttpResponse request = httpclient.execute(new HttpGet(url)); HttpResponse request = httpclient.execute(new HttpGet(url));
InputStream stream = request.getEntity().getContent(); InputStream stream = request.getEntity().getContent();
String appVersion[] = HttpHelper.convertStreamToString(stream).split("\\|"); String appVersion[] = HttpHelper.convertStreamToString(stream).split("\\|");

52
app/src/main/java/org/transdroid/core/service/BootReceiver.java

@ -16,13 +16,6 @@
*/ */
package org.transdroid.core.service; package org.transdroid.core.service;
import org.transdroid.core.app.settings.NotificationSettings;
import org.transdroid.core.app.settings.NotificationSettings_;
import org.transdroid.core.app.settings.SystemSettings;
import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.navigation.NavigationHelper_;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -30,10 +23,17 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.SystemClock; import android.os.SystemClock;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EReceiver;
import org.transdroid.core.app.settings.*;
import org.transdroid.core.gui.log.*;
import org.transdroid.core.gui.navigation.*;
/** /**
* Receives the intent that the device has been started in order to set up proper alarms for all background services. * Receives the intent that the device has been started in order to set up proper alarms for all background services.
* @author Eric Kok * @author Eric Kok
*/ */
@EReceiver
public class BootReceiver extends BroadcastReceiver { public class BootReceiver extends BroadcastReceiver {
public static final int ALARM_SERVERCHECKER = 0; public static final int ALARM_SERVERCHECKER = 0;
@ -42,26 +42,24 @@ public class BootReceiver extends BroadcastReceiver {
public static PendingIntent piServerChecker = null, piRssChecker = null, piAppUpdates = null; public static PendingIntent piServerChecker = null, piRssChecker = null, piAppUpdates = null;
@Override @Bean
public void onReceive(Context context, Intent intent) { protected Log log;
startBackgroundServices(context, false);
startAppUpdatesService(context);
}
public static void startBackgroundServices(Context context, boolean forceReload) { public static void startBackgroundServices(Context context, boolean forceReload) {
NotificationSettings notificationSettings = NotificationSettings_.getInstance_(context); NotificationSettings notificationSettings = NotificationSettings_.getInstance_(context);
AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// Start the alarms if one of the notifications are enabled and we do not yet have the alarms running // Start the alarms if one of the notifications are enabled and we do not yet have the alarms running
// (or should reload it forcefully) // (or should reload it forcefully)
if ((notificationSettings.isEnabledForRss() || notificationSettings.isEnabledForTorrents()) if ((notificationSettings.isEnabledForRss() || notificationSettings.isEnabledForTorrents()) &&
&& (forceReload || (piServerChecker == null && piRssChecker == null))) { (forceReload || (piServerChecker == null && piRssChecker == null))) {
Log.d(context, "Boot signal received, starting server and rss checker background services"); Log_.getInstance_(context)
.d("BootReceiver", "Boot signal received, starting server and rss checker background services");
// Schedule repeating alarms, with the first being (somewhat) in 1 second from now // Schedule repeating alarms, with the first being (somewhat) in 1 second from now
piServerChecker = PendingIntent.getBroadcast(context, ALARM_SERVERCHECKER, new Intent(context, piServerChecker = PendingIntent.getBroadcast(context, ALARM_SERVERCHECKER,
AlarmReceiver_.class).putExtra("service", ALARM_SERVERCHECKER), 0); new Intent(context, AlarmReceiver_.class).putExtra("service", ALARM_SERVERCHECKER), 0);
piRssChecker = PendingIntent.getBroadcast(context, ALARM_RSSCHECKER, new Intent(context, piRssChecker = PendingIntent.getBroadcast(context, ALARM_RSSCHECKER,
AlarmReceiver_.class).putExtra("service", ALARM_RSSCHECKER), 0); new Intent(context, AlarmReceiver_.class).putExtra("service", ALARM_RSSCHECKER), 0);
alarms.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, alarms.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000,
notificationSettings.getInvervalInMilliseconds(), piServerChecker); notificationSettings.getInvervalInMilliseconds(), piServerChecker);
alarms.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, alarms.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000,
@ -73,13 +71,13 @@ public class BootReceiver extends BroadcastReceiver {
public static void startAppUpdatesService(Context context) { public static void startAppUpdatesService(Context context) {
SystemSettings systemSettings = SystemSettings_.getInstance_(context); SystemSettings systemSettings = SystemSettings_.getInstance_(context);
AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (NavigationHelper_.getInstance_(context).enableUpdateChecker() && systemSettings.checkForUpdates() if (NavigationHelper_.getInstance_(context).enableUpdateChecker() && systemSettings.checkForUpdates() &&
&& piAppUpdates == null) { piAppUpdates == null) {
Log.d(context, "Boot signal received, starting app update checker service"); Log_.getInstance_(context).d("BootReceiver", "Boot signal received, starting app update checker service");
// Schedule a daily, with the first being (somewhat) in 1 second from now // Schedule a daily, with the first being (somewhat) in 1 second from now
piAppUpdates = PendingIntent.getBroadcast(context, ALARM_APPUPDATES, new Intent(context, piAppUpdates = PendingIntent.getBroadcast(context, ALARM_APPUPDATES,
AlarmReceiver_.class).putExtra("service", ALARM_APPUPDATES), 0); new Intent(context, AlarmReceiver_.class).putExtra("service", ALARM_APPUPDATES), 0);
alarms.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, alarms.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000,
AlarmManager.INTERVAL_DAY, piAppUpdates); AlarmManager.INTERVAL_DAY, piAppUpdates);
@ -106,4 +104,10 @@ public class BootReceiver extends BroadcastReceiver {
} }
} }
@Override
public void onReceive(Context context, Intent intent) {
startBackgroundServices(context, false);
startAppUpdatesService(context);
}
} }

50
app/src/main/java/org/transdroid/core/service/ControlService.java

@ -1,13 +1,16 @@
package org.transdroid.core.service; package org.transdroid.core.service;
import android.app.IntentService;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EService; import org.androidannotations.annotations.EService;
import org.transdroid.core.app.settings.ApplicationSettings; import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.core.widget.ListWidgetConfig; import org.transdroid.core.widget.ListWidgetConfig;
import org.transdroid.core.widget.ListWidgetProvider; import org.transdroid.core.widget.*;
import org.transdroid.core.widget.ListWidgetProvider_;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.task.DaemonTask; import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskResult;
@ -18,10 +21,6 @@ import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.task.StartAllTask; import org.transdroid.daemon.task.StartAllTask;
import org.transdroid.daemon.task.StopAllTask; import org.transdroid.daemon.task.StopAllTask;
import android.app.IntentService;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
@EService @EService
public class ControlService extends IntentService { public class ControlService extends IntentService {
@ -35,6 +34,8 @@ public class ControlService extends IntentService {
public static final String EXTRA_UPLOAD_RATE = "UPLOAD_RATE"; public static final String EXTRA_UPLOAD_RATE = "UPLOAD_RATE";
public static final String EXTRA_DOWNLOAD_RATE = "DOWNLOAD_RATE"; public static final String EXTRA_DOWNLOAD_RATE = "DOWNLOAD_RATE";
@Bean
protected Log log;
@Bean @Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@Bean @Bean
@ -47,11 +48,12 @@ public class ControlService extends IntentService {
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
if (intent == null) if (intent == null) {
return; return;
}
// We should have been supplied either am EXTRA_DAEMON or an AppWidgetManager.EXTRA_APPWIDGET_ID // We should have been supplied either am EXTRA_DAEMON or an AppWidgetManager.EXTRA_APPWIDGET_ID
ServerSetting server = null; ServerSetting server;
int appWidgetId = -1; int appWidgetId = -1;
if (intent.hasExtra(EXTRA_DAEMON)) { if (intent.hasExtra(EXTRA_DAEMON)) {
@ -59,8 +61,8 @@ public class ControlService extends IntentService {
int serverId = intent.getIntExtra(EXTRA_DAEMON, -1); int serverId = intent.getIntExtra(EXTRA_DAEMON, -1);
if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) { if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) {
// This server does not exist (any more) or no valid EXTRA_DAEMON value was supplied // This server does not exist (any more) or no valid EXTRA_DAEMON value was supplied
Log.e(this, "The control service can be started with a DAEMON extra zero-based server id, but the" log.e(this, "The control service can be started with a DAEMON extra zero-based server id, but the" +
+ "supplied id was invalid or no longer points to an existing server."); "supplied id was invalid or no longer points to an existing server.");
return; return;
} }
server = applicationSettings.getServerSetting(serverId); server = applicationSettings.getServerSetting(serverId);
@ -71,14 +73,14 @@ public class ControlService extends IntentService {
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
ListWidgetConfig config = applicationSettings.getWidgetConfig(appWidgetId); ListWidgetConfig config = applicationSettings.getWidgetConfig(appWidgetId);
if (config == null) { if (config == null) {
Log.e(this, log.e(this,
"The control service can be started by a widget using the AppWidgetManager.EXTRA_APPWIDGET_ID, " "The control service can be started by a widget using the AppWidgetManager.EXTRA_APPWIDGET_ID, " +
+ "but the id that was supplied does not point to an existing home screen widget."); "but the id that was supplied does not point to an existing home screen widget.");
return; return;
} }
int serverId = config.getServerId(); int serverId = config.getServerId();
if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) { if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) {
Log.e(this, "The home screen widget points to a server that no longer exists."); log.e(this, "The home screen widget points to a server that no longer exists.");
return; return;
} }
server = applicationSettings.getServerSetting(serverId); server = applicationSettings.getServerSetting(serverId);
@ -92,7 +94,7 @@ public class ControlService extends IntentService {
// Still no server? Then we don't have one specified yet // Still no server? Then we don't have one specified yet
if (server == null) { if (server == null) {
Log.e(this, "The control service was called, but there are nog servers configured at all."); log.e(this, "The control service was called, but there are nog servers configured at all.");
return; return;
} }
@ -111,24 +113,23 @@ public class ControlService extends IntentService {
// NOTE: If the upload or download rate was not specified, it will be reset on the server instead // NOTE: If the upload or download rate was not specified, it will be reset on the server instead
int uploadRate = intent.getIntExtra(EXTRA_UPLOAD_RATE, -1); int uploadRate = intent.getIntExtra(EXTRA_UPLOAD_RATE, -1);
int downloadRate = intent.getIntExtra(EXTRA_DOWNLOAD_RATE, -1); int downloadRate = intent.getIntExtra(EXTRA_DOWNLOAD_RATE, -1);
task = SetTransferRatesTask.create(adapter, uploadRate == -1 ? null : uploadRate, downloadRate == -1 ? null task = SetTransferRatesTask
: downloadRate); .create(adapter, uploadRate == -1 ? null : uploadRate, downloadRate == -1 ? null : downloadRate);
} }
// Execute the task, if we have one now // Execute the task, if we have one now
if (task == null) { if (task == null) {
Log.e(this, "The control service was started, but no (valid) action was specified, such as " log.e(this, "The control service was started, but no (valid) action was specified, such as " +
+ "org.transdroid.control.START_ALL or org.transdroid.control.SET_TRANSFER_RATES"); "org.transdroid.control.START_ALL or org.transdroid.control.SET_TRANSFER_RATES");
return; return;
} }
DaemonTaskResult result = task.execute(); DaemonTaskResult result = task.execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
Log.i(this, log.i(this,
task.getMethod().name() + " was successfully executed on " + server.getHumanReadableIdentifier()); task.getMethod().name() + " was successfully executed on " + server.getHumanReadableIdentifier());
} else { } else {
Log.i(this, log.i(this, task.getMethod().name() + " was NOT succcessfully executed on " +
task.getMethod().name() + " was NOT succcessfully executed on " server.getHumanReadableIdentifier() + " (and we are NOT trying again)");
+ server.getHumanReadableIdentifier() + " (and we are NOT trying again)");
// No need to continue now // No need to continue now
return; return;
} }
@ -140,6 +141,7 @@ public class ControlService extends IntentService {
try { try {
Thread.sleep(2000); Thread.sleep(2000);
} catch (Exception e) { } catch (Exception e) {
// Sleep
} }
// Ask the app widget provider to update this specific widget // Ask the app widget provider to update this specific widget

50
app/src/main/java/org/transdroid/core/service/RssCheckerService.java

@ -16,8 +16,12 @@
*/ */
package org.transdroid.core.service; package org.transdroid.core.service;
import java.util.LinkedHashSet; import android.app.IntentService;
import java.util.Set; import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EService; import org.androidannotations.annotations.EService;
@ -27,17 +31,13 @@ import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.NotificationSettings; import org.transdroid.core.app.settings.NotificationSettings;
import org.transdroid.core.app.settings.RssfeedSetting; import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.rss.RssfeedsActivity_; import org.transdroid.core.gui.rss.*;
import org.transdroid.core.rssparser.Item; import org.transdroid.core.rssparser.Item;
import org.transdroid.core.rssparser.RssParser; import org.transdroid.core.rssparser.RssParser;
import org.transdroid.daemon.util.Collections2; import org.transdroid.daemon.util.Collections2;
import android.app.IntentService; import java.util.LinkedHashSet;
import android.app.Notification; import java.util.Set;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
/** /**
* A background service that checks all user-configured RSS feeds for new items. * A background service that checks all user-configured RSS feeds for new items.
@ -46,6 +46,8 @@ import android.content.Intent;
@EService @EService
public class RssCheckerService extends IntentService { public class RssCheckerService extends IntentService {
@Bean
protected Log log;
@Bean @Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@Bean @Bean
@ -64,7 +66,7 @@ public class RssCheckerService extends IntentService {
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
if (!connectivityHelper.shouldPerformBackgroundActions() || !notificationSettings.isEnabledForRss()) { if (!connectivityHelper.shouldPerformBackgroundActions() || !notificationSettings.isEnabledForRss()) {
Log.d(this, log.d(this,
"Skip the RSS checker service, as background data is disabled, the service is disabled or we are not connected."); "Skip the RSS checker service, as background data is disabled, the service is disabled or we are not connected.");
return; return;
} }
@ -76,15 +78,16 @@ public class RssCheckerService extends IntentService {
try { try {
if (!feed.shouldAlarmOnNewItems()) { if (!feed.shouldAlarmOnNewItems()) {
Log.d(this, "Skip checker for " + feed.getName() + " as alarms are disabled"); log.d(this, "Skip checker for " + feed.getName() + " as alarms are disabled");
continue; continue;
} }
Log.d(this, "Try to parse " + feed.getName() + " (" + feed.getUrl() + ")"); log.d(this, "Try to parse " + feed.getName() + " (" + feed.getUrl() + ")");
RssParser parser = new RssParser(feed.getUrl()); RssParser parser = new RssParser(feed.getUrl());
parser.parse(); parser.parse();
if (parser.getChannel() == null) if (parser.getChannel() == null) {
continue; continue;
}
// Find the last item that is newer than the last viewed date // Find the last item that is newer than the last viewed date
for (Item item : parser.getChannel().getItems()) { for (Item item : parser.getChannel().getItems()) {
@ -92,13 +95,14 @@ public class RssCheckerService extends IntentService {
break; break;
} else { } else {
unread++; unread++;
if (!hasUnread.contains(feed.getName())) if (!hasUnread.contains(feed.getName())) {
hasUnread.add(feed.getName()); hasUnread.add(feed.getName());
} }
} }
}
Log.d(this, feed.getName() + " has " + (hasUnread.contains(feed.getName()) ? "" : "no ") log.d(this,
+ "unread items"); feed.getName() + " has " + (hasUnread.contains(feed.getName()) ? "" : "no ") + "unread items");
} catch (Exception e) { } catch (Exception e) {
// Ignore RSS feeds that could not be retrieved or parsed // Ignore RSS feeds that could not be retrieved or parsed
@ -111,17 +115,17 @@ public class RssCheckerService extends IntentService {
} }
// Provide a notification, since there are new RSS items // Provide a notification, since there are new RSS items
PendingIntent pi = PendingIntent.getActivity(this, 80000, new Intent(this, RssfeedsActivity_.class), PendingIntent pi = PendingIntent
PendingIntent.FLAG_UPDATE_CURRENT); .getActivity(this, 80000, new Intent(this, RssfeedsActivity_.class), PendingIntent.FLAG_UPDATE_CURRENT);
String title = getResources().getQuantityString(R.plurals.rss_service_new, unread, Integer.toString(unread)); String title = getResources().getQuantityString(R.plurals.rss_service_new, unread, Integer.toString(unread));
String forString = Collections2.joinString(hasUnread, ", "); String forString = Collections2.joinString(hasUnread, ", ");
Builder builder = new Notification.Builder(this).setSmallIcon(R.drawable.ic_stat_notification) Builder builder = new Notification.Builder(this).setSmallIcon(R.drawable.ic_stat_notification).setTicker(title)
.setTicker(title).setContentTitle(title) .setContentTitle(title).setContentText(getString(R.string.rss_service_newfor, forString))
.setContentText(getString(R.string.rss_service_newfor, forString)).setNumber(unread) .setNumber(unread).setLights(notificationSettings.getDesiredLedColour(), 600, 1000)
.setLights(notificationSettings.getDesiredLedColour(), 600, 1000)
.setSound(notificationSettings.getSound()).setAutoCancel(true).setContentIntent(pi); .setSound(notificationSettings.getSound()).setAutoCancel(true).setContentIntent(pi);
if (notificationSettings.shouldVibrate()) if (notificationSettings.shouldVibrate()) {
builder.setVibrate(notificationSettings.getDefaultVibratePattern()); builder.setVibrate(notificationSettings.getDefaultVibratePattern());
}
notificationManager.notify(80001, builder.getNotification()); notificationManager.notify(80001, builder.getNotification());
} }

10
app/src/main/java/org/transdroid/core/service/ServerCheckerService.java

@ -54,6 +54,8 @@ import android.os.Build;
@EService @EService
public class ServerCheckerService extends IntentService { public class ServerCheckerService extends IntentService {
@Bean
protected Log log;
@Bean @Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@Bean @Bean
@ -73,7 +75,7 @@ public class ServerCheckerService extends IntentService {
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
if (!connectivityHelper.shouldPerformBackgroundActions() || !notificationSettings.isEnabledForTorrents()) { if (!connectivityHelper.shouldPerformBackgroundActions() || !notificationSettings.isEnabledForTorrents()) {
Log.d(this, log.d(this,
"Skip the server checker service, as background data is disabled, the service is disabled or we are not connected."); "Skip the server checker service, as background data is disabled, the service is disabled or we are not connected.");
return; return;
} }
@ -92,13 +94,13 @@ public class ServerCheckerService extends IntentService {
// Synchronously retrieve torrents listing // Synchronously retrieve torrents listing
IDaemonAdapter adapter = server.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this); IDaemonAdapter adapter = server.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
DaemonTaskResult result = RetrieveTask.create(adapter).execute(); DaemonTaskResult result = RetrieveTask.create(adapter).execute(log);
if (!(result instanceof RetrieveTaskSuccessResult)) { if (!(result instanceof RetrieveTaskSuccessResult)) {
// Cannot retrieve torrents at this time // Cannot retrieve torrents at this time
continue; continue;
} }
List<Torrent> retrieved = ((RetrieveTaskSuccessResult) result).getTorrents(); List<Torrent> retrieved = ((RetrieveTaskSuccessResult) result).getTorrents();
Log.d(this, server.getName() + ": Retrieved torrent listing"); log.d(this, server.getName() + ": Retrieved torrent listing");
// Check for differences between the last and the current stats // Check for differences between the last and the current stats
JSONArray currentStats = new JSONArray(); JSONArray currentStats = new JSONArray();
@ -133,7 +135,7 @@ public class ServerCheckerService extends IntentService {
applicationSettings.setServerLastStats(server, currentStats); applicationSettings.setServerLastStats(server, currentStats);
// Notify on new and now-done torrents for this server // Notify on new and now-done torrents for this server
Log.d(this, server.getName() + ": " + newTorrents.size() + " new torrents, " + doneTorrents.size() log.d(this, server.getName() + ": " + newTorrents.size() + " new torrents, " + doneTorrents.size()
+ " newly finished torrents."); + " newly finished torrents.");
Intent i = new Intent(this, TorrentsActivity_.class); Intent i = new Intent(this, TorrentsActivity_.class);
i.putExtra("org.transdroid.START_SERVER", server.getOrder()); i.putExtra("org.transdroid.START_SERVER", server.getOrder());

176
app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java

@ -16,9 +16,21 @@
*/ */
package org.transdroid.core.widget; package org.transdroid.core.widget;
import java.util.ArrayList; import android.annotation.TargetApi;
import java.util.Collections; import android.app.ActionBar;
import java.util.List; import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Background;
@ -33,6 +45,7 @@ import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.app.settings.SystemSettings; import org.transdroid.core.app.settings.SystemSettings;
import org.transdroid.core.gui.lists.SimpleListItemSpinnerAdapter; import org.transdroid.core.gui.lists.SimpleListItemSpinnerAdapter;
import org.transdroid.core.gui.lists.SortByListItem; import org.transdroid.core.gui.lists.SortByListItem;
import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.navigation.StatusType; import org.transdroid.core.gui.navigation.StatusType;
import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter; import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter;
import org.transdroid.core.service.ConnectivityHelper; import org.transdroid.core.service.ConnectivityHelper;
@ -47,21 +60,9 @@ import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.util.FileSizeConverter; import org.transdroid.daemon.util.FileSizeConverter;
import android.annotation.TargetApi; import java.util.ArrayList;
import android.app.ActionBar; import java.util.Collections;
import android.app.Activity; import java.util.List;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@EActivity(resName = "activity_widgetconfig") @EActivity(resName = "activity_widgetconfig")
@ -80,23 +81,73 @@ public class ListWidgetConfigActivity extends Activity {
protected ListView torrentsList; protected ListView torrentsList;
@ViewById @ViewById
protected View navigationView, serverstatusView; protected View navigationView, serverstatusView;
private List<Torrent> previewTorrents = null;
// Settings and helpers // Settings and helpers
@Bean @Bean
protected Log log;
@Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
@Bean @Bean
protected SystemSettings systemSettings; protected SystemSettings systemSettings;
protected OnCheckedChangeListener reverseorderCheckedChanged = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
filterTorrents();
}
};
protected OnCheckedChangeListener showstatusCheckChanged = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
navigationView.setVisibility(showstatusCheckBox.isChecked() ? View.GONE : View.VISIBLE);
serverstatusView.setVisibility(showstatusCheckBox.isChecked() ? View.VISIBLE : View.GONE);
}
};
private List<Torrent> previewTorrents = null;
private int appWidgetId; private int appWidgetId;
private OnClickListener doneClicked = new OnClickListener() {
@Override
public void onClick(View v) {
// All spinner have to be initialised already
if (serverSpinner.getSelectedItem() == null) {
return;
}
if (filterSpinner.getSelectedItem() == null) {
return;
}
if (sortSpinner.getSelectedItem() == null) {
return;
}
// Store these user preferences
int server = ((ServerSetting) serverSpinner.getSelectedItem()).getOrder();
StatusType statusType = ((StatusTypeFilter) filterSpinner.getSelectedItem()).getStatusType();
TorrentsSortBy sortBy = ((SortByListItem) sortSpinner.getSelectedItem()).getSortBy();
boolean reverseSort = reverseorderCheckBox.isChecked();
boolean showstatus = showstatusCheckBox.isChecked();
boolean useDarkTheme = darkthemeCheckBox.isChecked();
ListWidgetConfig config =
new ListWidgetConfig(server, statusType, sortBy, reverseSort, showstatus, useDarkTheme);
applicationSettings.setWidgetConfig(appWidgetId, config);
// Return the widget configuration result
AppWidgetManager manager = AppWidgetManager.getInstance(ListWidgetConfigActivity.this);
manager.updateAppWidget(appWidgetId,
ListWidgetProvider.buildRemoteViews(getApplicationContext(), appWidgetId, config));
manager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.torrents_list);
setResult(RESULT_OK, new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId));
finish();
}
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (getIntent() == null || getIntent().getExtras() == null if (getIntent() == null || getIntent().getExtras() == null ||
|| !getIntent().hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { !getIntent().hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
// Invalid configuration; return canceled result // Invalid configuration; return canceled result
setResult(RESULT_CANCELED, setResult(RESULT_CANCELED,
new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)); new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID));
@ -104,8 +155,8 @@ public class ListWidgetConfigActivity extends Activity {
} }
// Get the appwidget ID we are configuring // Get the appwidget ID we are configuring
appWidgetId = getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId =
AppWidgetManager.INVALID_APPWIDGET_ID); getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
// Set preliminary canceled result and continue with the initialisation // Set preliminary canceled result and continue with the initialisation
setResult(RESULT_CANCELED, new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)); setResult(RESULT_CANCELED, new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId));
@ -119,10 +170,10 @@ public class ListWidgetConfigActivity extends Activity {
for (TorrentsSortBy order : TorrentsSortBy.values()) { for (TorrentsSortBy order : TorrentsSortBy.values()) {
sortOrders.add(new SortByListItem(this, order)); sortOrders.add(new SortByListItem(this, order));
} }
serverSpinner.setAdapter(new SimpleListItemSpinnerAdapter<ServerSetting>(this, 0, applicationSettings serverSpinner.setAdapter(
.getAllServerSettings())); new SimpleListItemSpinnerAdapter<ServerSetting>(this, 0, applicationSettings.getAllServerSettings()));
filterSpinner.setAdapter(new SimpleListItemSpinnerAdapter<StatusTypeFilter>(this, 0, StatusType filterSpinner.setAdapter(
.getAllStatusTypes(this))); new SimpleListItemSpinnerAdapter<StatusTypeFilter>(this, 0, StatusType.getAllStatusTypes(this)));
sortSpinner.setAdapter(new SimpleListItemSpinnerAdapter<SortByListItem>(this, 0, sortOrders)); sortSpinner.setAdapter(new SimpleListItemSpinnerAdapter<SortByListItem>(this, 0, sortOrders));
// TODO: Update to AndroidAnnotations 3.0 and use @CheckedChanged // TODO: Update to AndroidAnnotations 3.0 and use @CheckedChanged
reverseorderCheckBox.setOnCheckedChangeListener(reverseorderCheckedChanged); reverseorderCheckBox.setOnCheckedChangeListener(reverseorderCheckedChanged);
@ -156,31 +207,17 @@ public class ListWidgetConfigActivity extends Activity {
filterTorrents(); filterTorrents();
} }
protected OnCheckedChangeListener reverseorderCheckedChanged = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
filterTorrents();
}
};
protected OnCheckedChangeListener showstatusCheckChanged = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
navigationView.setVisibility(showstatusCheckBox.isChecked()? View.GONE: View.VISIBLE);
serverstatusView.setVisibility(showstatusCheckBox.isChecked()? View.VISIBLE: View.GONE);
}
};
@Background @Background
protected void loadTorrents() { protected void loadTorrents() {
if (serverSpinner.getSelectedItem() == null) if (serverSpinner.getSelectedItem() == null) {
return; return;
}
// Create a connection object and retrieve the live torrents // Create a connection object and retrieve the live torrents
IDaemonAdapter connection = ((ServerSetting) serverSpinner.getSelectedItem()) IDaemonAdapter connection = ((ServerSetting) serverSpinner.getSelectedItem())
.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this); .createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
DaemonTaskResult result = RetrieveTask.create(connection).execute(); DaemonTaskResult result = RetrieveTask.create(connection).execute(log);
if (result instanceof RetrieveTaskSuccessResult) { if (result instanceof RetrieveTaskSuccessResult) {
// Success; show the active torrents in the widget preview // Success; show the active torrents in the widget preview
onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(),
@ -201,23 +238,28 @@ public class ListWidgetConfigActivity extends Activity {
protected void filterTorrents() { protected void filterTorrents() {
// All spinners have to be initialised already // All spinners have to be initialised already
if (serverSpinner.getSelectedItem() == null) if (serverSpinner.getSelectedItem() == null) {
return; return;
if (filterSpinner.getSelectedItem() == null) }
if (filterSpinner.getSelectedItem() == null) {
return; return;
if (sortSpinner.getSelectedItem() == null) }
if (sortSpinner.getSelectedItem() == null) {
return; return;
if (previewTorrents == null) }
if (previewTorrents == null) {
return; return;
}
// Get the already loaded torrents and filter and sort them // Get the already loaded torrents and filter and sort them
ArrayList<Torrent> filteredTorrents = new ArrayList<Torrent>(previewTorrents.size()); ArrayList<Torrent> filteredTorrents = new ArrayList<Torrent>(previewTorrents.size());
StatusTypeFilter statusTypeFilter = (StatusTypeFilter) filterSpinner.getSelectedItem(); StatusTypeFilter statusTypeFilter = (StatusTypeFilter) filterSpinner.getSelectedItem();
boolean dormantAsInactive = systemSettings.treatDormantAsInactive(); boolean dormantAsInactive = systemSettings.treatDormantAsInactive();
for (Torrent torrent : previewTorrents) { for (Torrent torrent : previewTorrents) {
if (statusTypeFilter.matches(torrent, dormantAsInactive)) if (statusTypeFilter.matches(torrent, dormantAsInactive)) {
filteredTorrents.add(torrent); filteredTorrents.add(torrent);
} }
}
if (filteredTorrents.size() == 0) { if (filteredTorrents.size() == 0) {
showError(true); showError(true);
return; return;
@ -257,38 +299,4 @@ public class ListWidgetConfigActivity extends Activity {
errorText.setText(emptyResults ? R.string.navigation_emptytorrents : R.string.error_httperror); errorText.setText(emptyResults ? R.string.navigation_emptytorrents : R.string.error_httperror);
} }
private OnClickListener doneClicked = new OnClickListener() {
@Override
public void onClick(View v) {
// All spinner have to be initialised already
if (serverSpinner.getSelectedItem() == null)
return;
if (filterSpinner.getSelectedItem() == null)
return;
if (sortSpinner.getSelectedItem() == null)
return;
// Store these user preferences
int server = ((ServerSetting) serverSpinner.getSelectedItem()).getOrder();
StatusType statusType = ((StatusTypeFilter) filterSpinner.getSelectedItem()).getStatusType();
TorrentsSortBy sortBy = ((SortByListItem) sortSpinner.getSelectedItem()).getSortBy();
boolean reverseSort = reverseorderCheckBox.isChecked();
boolean showstatus = showstatusCheckBox.isChecked();
boolean useDarkTheme = darkthemeCheckBox.isChecked();
ListWidgetConfig config = new ListWidgetConfig(server, statusType, sortBy, reverseSort, showstatus,
useDarkTheme);
applicationSettings.setWidgetConfig(appWidgetId, config);
// Return the widget configuration result
AppWidgetManager manager = AppWidgetManager.getInstance(ListWidgetConfigActivity.this);
manager.updateAppWidget(appWidgetId,
ListWidgetProvider.buildRemoteViews(getApplicationContext(), appWidgetId, config));
manager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.torrents_list);
setResult(RESULT_OK, new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId));
finish();
}
};
} }

111
app/src/main/java/org/transdroid/core/widget/ListWidgetProvider.java

@ -16,15 +16,6 @@
*/ */
package org.transdroid.core.widget; package org.transdroid.core.widget;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EReceiver;
import org.transdroid.R;
import org.transdroid.core.app.settings.*;
import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.gui.*;
import org.transdroid.core.gui.log.Log;
import org.transdroid.core.service.ControlService;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
@ -33,8 +24,17 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EReceiver;
import org.transdroid.R;
import org.transdroid.core.app.settings.*;
import org.transdroid.core.gui.*;
import org.transdroid.core.gui.log.*;
import org.transdroid.core.service.ControlService;
/** /**
* The provider of a list-style Transdroid widget, which controls the general loading and (touch) event handling. The * The provider of a list-style Transdroid widget, which controls the general loading and (touch) event handling. The
* list rows' remote views are loaded in the accompanying {@link ListWidgetViewsService}. * list rows' remote views are loaded in the accompanying {@link ListWidgetViewsService}.
@ -52,49 +52,6 @@ public class ListWidgetProvider extends AppWidgetProvider {
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent == null)
return;
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
// Refresh a specific app widget
if (intent.hasExtra(EXTRA_REFRESH)) {
// Manually requested a refresh for the app widget of which the ID was supplied
RemoteViews views = buildRemoteViews(context, appWidgetId, applicationSettings.getWidgetConfig(appWidgetId));
if (views != null) {
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.torrents_list);
}
return;
}
// No refresh: this is a control intent: copy the action and EXTRA_APPWIDGET_ID to start the control service
if (intent.getAction().startsWith("org.transdroid.control.")) {
Intent action = new Intent(intent.getAction());
action.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
context.startService(action);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
appWidgetManager.updateAppWidget(appWidgetId,
buildRemoteViews(context, appWidgetId, applicationSettings.getWidgetConfig(appWidgetId)));
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.torrents_list);
}
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
applicationSettings.removeWidgetConfig(appWidgetId);
}
}
/** /**
* Loads and sets up the layout for some specific app widget given the user's widget settings. Note that the views * Loads and sets up the layout for some specific app widget given the user's widget settings. Note that the views
* for the list view rows are loaded separately in the {@link WidgetViewsFactory}. * for the list view rows are loaded separately in the {@link WidgetViewsFactory}.
@ -107,12 +64,14 @@ public class ListWidgetProvider extends AppWidgetProvider {
public static RemoteViews buildRemoteViews(Context context, int appWidgetId, ListWidgetConfig config) { public static RemoteViews buildRemoteViews(Context context, int appWidgetId, ListWidgetConfig config) {
// Does the server to show and its widget settings actually still exist? // Does the server to show and its widget settings actually still exist?
if (context == null || config == null) if (context == null || config == null) {
return null; return null;
}
ApplicationSettings appSettings = ApplicationSettings_.getInstance_(context); ApplicationSettings appSettings = ApplicationSettings_.getInstance_(context);
if (config.getServerId() < 0 || config.getServerId() > appSettings.getMaxOfAllServers()) { if (config.getServerId() < 0 || config.getServerId() > appSettings.getMaxOfAllServers()) {
Log.e(context, "Tried to set up widget " + appWidgetId + " but the bound server ID " + config.getServerId() Log_.getInstance_(context).e("ListWidgetProvider",
+ " no longer exists."); "Tried to set up widget " + appWidgetId + " but the bound server ID " + config.getServerId() +
" no longer exists.");
return null; return null;
} }
@ -176,4 +135,46 @@ public class ListWidgetProvider extends AppWidgetProvider {
} }
@Override
public void onReceive(Context context, @NonNull Intent intent) {
super.onReceive(context, intent);
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
// Refresh a specific app widget
if (intent.hasExtra(EXTRA_REFRESH)) {
// Manually requested a refresh for the app widget of which the ID was supplied
RemoteViews views =
buildRemoteViews(context, appWidgetId, applicationSettings.getWidgetConfig(appWidgetId));
if (views != null) {
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.torrents_list);
}
return;
}
// No refresh: this is a control intent: copy the action and EXTRA_APPWIDGET_ID to start the control service
if (intent.getAction().startsWith("org.transdroid.control.")) {
Intent action = new Intent(intent.getAction());
action.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
context.startService(action);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
appWidgetManager.updateAppWidget(appWidgetId,
buildRemoteViews(context, appWidgetId, applicationSettings.getWidgetConfig(appWidgetId)));
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.torrents_list);
}
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
applicationSettings.removeWidgetConfig(appWidgetId);
}
}
} }

66
app/src/main/java/org/transdroid/core/widget/ListWidgetViewsService.java

@ -16,16 +16,26 @@
*/ */
package org.transdroid.core.widget; package org.transdroid.core.widget;
import java.util.ArrayList; import android.annotation.TargetApi;
import java.util.Collections; import android.appwidget.AppWidgetManager;
import java.util.List; import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import org.androidannotations.annotations.EService; import org.androidannotations.annotations.EService;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.*; import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.ApplicationSettings_;
import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.app.settings.SystemSettings;
import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.lists.LocalTorrent; import org.transdroid.core.gui.lists.LocalTorrent;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.core.service.*; import org.transdroid.core.gui.log.Log_;
import org.transdroid.core.service.ConnectivityHelper_;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
@ -35,14 +45,9 @@ import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.util.FileSizeConverter; import org.transdroid.daemon.util.FileSizeConverter;
import android.annotation.TargetApi; import java.util.ArrayList;
import android.appwidget.AppWidgetManager; import java.util.Collections;
import android.content.Context; import java.util.List;
import android.content.Intent;
import android.os.Build;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
/** /**
* A service for the list widget to update the remote views that a list widget shows, by getting the torrents from the * A service for the list widget to update the remote views that a list widget shows, by getting the torrents from the
@ -65,13 +70,15 @@ class WidgetViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private final Context context; private final Context context;
private final int appWidgetId; private final int appWidgetId;
private final Log log;
private List<Torrent> torrents = null; private List<Torrent> torrents = null;
private ListWidgetConfig config = null; private ListWidgetConfig config = null;
public WidgetViewsFactory(Context applicationContext, Intent intent) { public WidgetViewsFactory(Context applicationContext, Intent intent) {
this.context = applicationContext; this.context = applicationContext;
this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, this.appWidgetId =
AppWidgetManager.INVALID_APPWIDGET_ID); intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
this.log = Log_.getInstance_(applicationContext);
} }
@Override @Override
@ -86,27 +93,30 @@ class WidgetViewsFactory implements RemoteViewsService.RemoteViewsFactory {
ApplicationSettings settings = ApplicationSettings_.getInstance_(context); ApplicationSettings settings = ApplicationSettings_.getInstance_(context);
config = settings.getWidgetConfig(appWidgetId); config = settings.getWidgetConfig(appWidgetId);
if (config == null || config.getServerId() < 0) { if (config == null || config.getServerId() < 0) {
Log.e(context, "Looking for widget data while the widget wasn't yet configured"); log.e(context, "Looking for widget data while the widget wasn't yet configured");
return; return;
} }
ServerSetting server = settings.getServerSetting(config.getServerId()); ServerSetting server = settings.getServerSetting(config.getServerId());
if (server == null) { if (server == null) {
// TODO: Show error text some how in the remote view, perhaps via the EmptyView's text? // TODO: Show error text some how in the remote view, perhaps via the EmptyView's text?
Log.e(context, "The server for which this widget was created no longer exists"); log.e(context, "The server for which this widget was created no longer exists");
if (torrents != null) if (torrents != null) {
torrents.clear(); torrents.clear();
}
return; return;
} }
// Load the torrents; synchronously // Load the torrents; synchronously
IDaemonAdapter connection = server.createServerAdapter(ConnectivityHelper_.getInstance_(context) IDaemonAdapter connection =
.getConnectedNetworkName(), context); server.createServerAdapter(ConnectivityHelper_.getInstance_(context).getConnectedNetworkName(),
DaemonTaskResult result = RetrieveTask.create(connection).execute(); context);
DaemonTaskResult result = RetrieveTask.create(connection).execute(log);
if (!(result instanceof RetrieveTaskSuccessResult)) { if (!(result instanceof RetrieveTaskSuccessResult)) {
// TODO: Show error text somehow in the remote view, perhaps via the EmptyView's text? // TODO: Show error text somehow in the remote view, perhaps via the EmptyView's text?
Log.e(context, "The torrents could not be retrieved at this time; probably a connection issue"); log.e(context, "The torrents could not be retrieved at this time; probably a connection issue");
if (torrents != null) if (torrents != null) {
torrents.clear(); torrents.clear();
}
return; return;
} }
@ -115,9 +125,11 @@ class WidgetViewsFactory implements RemoteViewsService.RemoteViewsFactory {
ArrayList<Torrent> filteredTorrents = new ArrayList<Torrent>(); ArrayList<Torrent> filteredTorrents = new ArrayList<Torrent>();
List<Torrent> allTorrents = ((RetrieveTaskSuccessResult) result).getTorrents(); List<Torrent> allTorrents = ((RetrieveTaskSuccessResult) result).getTorrents();
for (Torrent torrent : allTorrents) { for (Torrent torrent : allTorrents) {
if (config.getStatusType().getFilterItem(context).matches(torrent, systemSettings.treatDormantAsInactive())) if (config.getStatusType().getFilterItem(context)
.matches(torrent, systemSettings.treatDormantAsInactive())) {
filteredTorrents.add(torrent); filteredTorrents.add(torrent);
} }
}
if (filteredTorrents.size() > 0) { if (filteredTorrents.size() > 0) {
// Only sort when there are actually torrents left after filtering // Only sort when there are actually torrents left after filtering
Daemon serverType = filteredTorrents.get(0).getDaemon(); Daemon serverType = filteredTorrents.get(0).getDaemon();
@ -203,8 +215,9 @@ class WidgetViewsFactory implements RemoteViewsService.RemoteViewsFactory {
@Override @Override
public void onDestroy() { public void onDestroy() {
if (torrents != null) if (torrents != null) {
torrents.clear(); torrents.clear();
}
torrents = null; torrents = null;
} }
@ -220,8 +233,9 @@ class WidgetViewsFactory implements RemoteViewsService.RemoteViewsFactory {
@Override @Override
public int getCount() { public int getCount() {
if (torrents == null) if (torrents == null) {
return 0; return 0;
}
return torrents.size(); return torrents.size();
} }

16
app/src/main/java/org/transdroid/daemon/AlphanumComparator.java

@ -29,12 +29,12 @@ import java.util.Comparator;
public class AlphanumComparator implements Comparator<String> { public class AlphanumComparator implements Comparator<String> {
private final boolean isDigit(char ch) { private boolean isDigit(char ch) {
return ch >= 48 && ch <= 57; return ch >= 48 && ch <= 57;
} }
/** Length of string is passed in for improved efficiency (only need to calculate it once) **/ /** Length of string is passed in for improved efficiency (only need to calculate it once) **/
private final String getChunk(String s, int slength, int marker) { private String getChunk(String s, int slength, int marker) {
StringBuilder chunk = new StringBuilder(); StringBuilder chunk = new StringBuilder();
char c = s.charAt(marker); char c = s.charAt(marker);
chunk.append(c); chunk.append(c);
@ -59,12 +59,14 @@ public class AlphanumComparator implements Comparator<String> {
return chunk.toString(); return chunk.toString();
} }
public int compare(String o1, String o2) { public int compare(String s1, String s2) {
if (!(o1 instanceof String) || !(o2 instanceof String)) { if (s1 == null && s2 != null) {
return -1;
} else if (s1 != null && s2 == null) {
return 1;
} else if (s1 == null) {
return 0; return 0;
} }
String s1 = (String) o1;
String s2 = (String) o2;
int thisMarker = 0; int thisMarker = 0;
int thatMarker = 0; int thatMarker = 0;
@ -79,7 +81,7 @@ public class AlphanumComparator implements Comparator<String> {
thatMarker += thatChunk.length(); thatMarker += thatChunk.length();
// If both chunks contain numeric characters, sort them numerically // If both chunks contain numeric characters, sort them numerically
int result = 0; int result;
if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) { if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) {
// Simple chunk comparison by length. // Simple chunk comparison by length.
int thisChunkLength = thisChunk.length(); int thisChunkLength = thisChunk.length();

130
app/src/main/java/org/transdroid/daemon/Aria2c/Aria2Adapter.java

@ -17,15 +17,8 @@
*/ */
package org.transdroid.daemon.Aria2c; package org.transdroid.daemon.Aria2c;
import java.io.File; import android.net.Uri;
import java.io.FileInputStream; import android.text.TextUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -36,6 +29,7 @@ import org.base64.android.Base64;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
@ -63,15 +57,20 @@ import org.transdroid.daemon.task.ResumeTask;
import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import android.net.Uri; import java.io.File;
import android.text.TextUtils; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/** /**
* The daemon adapter from the Aria2 torrent client. Documentation available at * The daemon adapter from the Aria2 torrent client. Documentation available at http://aria2.sourceforge.net/manual/en/html/aria2c.html
* http://aria2.sourceforge.net/manual/en/html/aria2c.html
* @author erickok * @author erickok
*/ */
public class Aria2Adapter implements IDaemonAdapter { public class Aria2Adapter implements IDaemonAdapter {
@ -86,7 +85,7 @@ public class Aria2Adapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
JSONArray params = new JSONArray(); JSONArray params = new JSONArray();
@ -96,16 +95,19 @@ public class Aria2Adapter implements IDaemonAdapter {
// Request all torrents from server // Request all torrents from server
// NOTE Since there is no aria2.tellAll (or something) we have to use batch requests // NOTE Since there is no aria2.tellAll (or something) we have to use batch requests
JSONArray fields = new JSONArray().put("gid").put("status").put("totalLength").put("completedLength") JSONArray fields =
.put("uploadLength").put("downloadSpeed").put("uploadSpeed").put("numSeeders").put("dir") new JSONArray().put("gid").put("status").put("totalLength").put("completedLength")
.put("connections").put("errorCode").put("bittorrent").put("files"); .put("uploadLength").put("downloadSpeed").put("uploadSpeed").put("numSeeders")
.put("dir").put("connections").put("errorCode").put("bittorrent").put("files");
JSONObject active = buildRequest("aria2.tellActive", new JSONArray().put(fields)); JSONObject active = buildRequest("aria2.tellActive", new JSONArray().put(fields));
JSONObject waiting = buildRequest("aria2.tellWaiting", new JSONArray().put(0).put(9999).put(fields)); JSONObject waiting =
JSONObject stopped = buildRequest("aria2.tellStopped", new JSONArray().put(0).put(9999).put(fields)); buildRequest("aria2.tellWaiting", new JSONArray().put(0).put(9999).put(fields));
JSONObject stopped =
buildRequest("aria2.tellStopped", new JSONArray().put(0).put(9999).put(fields));
params.put(active).put(waiting).put(stopped); params.put(active).put(waiting).put(stopped);
List<Torrent> torrents = new ArrayList<Torrent>(); List<Torrent> torrents = new ArrayList<Torrent>();
JSONArray lists = makeRequestForArray(params.toString()); JSONArray lists = makeRequestForArray(log, params.toString());
for (int i = 0; i < lists.length(); i++) { for (int i = 0; i < lists.length(); i++) {
torrents.addAll(parseJsonRetrieveTorrents(lists.getJSONObject(i).getJSONArray("result"))); torrents.addAll(parseJsonRetrieveTorrents(lists.getJSONObject(i).getJSONArray("result")));
} }
@ -117,7 +119,7 @@ public class Aria2Adapter implements IDaemonAdapter {
params.put(task.getTargetTorrent().getUniqueID()); // gid params.put(task.getTargetTorrent().getUniqueID()); // gid
params.put(new JSONArray().put("bittorrent").put("errorCode")); params.put(new JSONArray().put("bittorrent").put("errorCode"));
JSONObject dinfo = makeRequest(buildRequest("aria2.tellStatus", params).toString()); JSONObject dinfo = makeRequest(log, buildRequest("aria2.tellStatus", params).toString());
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
parseJsonTorrentDetails(dinfo.getJSONObject("result"))); parseJsonTorrentDetails(dinfo.getJSONObject("result")));
@ -126,15 +128,16 @@ public class Aria2Adapter implements IDaemonAdapter {
// Request file listing of a torrent // Request file listing of a torrent
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
JSONObject finfo = makeRequest(buildRequest("aria2.getFiles", params).toString()); JSONObject finfo = makeRequest(log, buildRequest("aria2.getFiles", params).toString());
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFileListing( return new GetFileListTaskSuccessResult((GetFileListTask) task,
finfo.getJSONArray("result"), task.getTargetTorrent())); parseJsonFileListing(finfo.getJSONArray("result"), task.getTargetTorrent()));
case AddByFile: case AddByFile:
// Encode the .torrent file's data // Encode the .torrent file's data
String file = ((AddByFileTask) task).getFile(); String file = ((AddByFileTask) task).getFile();
InputStream in = new Base64.InputStream(new FileInputStream(new File(URI.create(file))), Base64.ENCODE); InputStream in =
new Base64.InputStream(new FileInputStream(new File(URI.create(file))), Base64.ENCODE);
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
int c; int c;
while ((c = in.read()) != -1) { while ((c = in.read()) != -1) {
@ -144,7 +147,7 @@ public class Aria2Adapter implements IDaemonAdapter {
// Request to add a torrent by local .torrent file // Request to add a torrent by local .torrent file
params.put(writer.toString()); params.put(writer.toString());
makeRequest(buildRequest("aria2.addTorrent", params).toString()); makeRequest(log, buildRequest("aria2.addTorrent", params).toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
@ -153,7 +156,7 @@ public class Aria2Adapter implements IDaemonAdapter {
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
params.put(new JSONArray().put(url)); params.put(new JSONArray().put(url));
makeRequest(buildRequest("aria2.addUri", params).toString()); makeRequest(log, buildRequest("aria2.addUri", params).toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
@ -162,14 +165,15 @@ public class Aria2Adapter implements IDaemonAdapter {
String magnet = ((AddByMagnetUrlTask) task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
params.put(new JSONArray().put(magnet)); params.put(new JSONArray().put(magnet));
makeRequest(buildRequest("aria2.addUri", params).toString()); makeRequest(log, buildRequest("aria2.addUri", params).toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeRequest(buildRequest(removeTask.includingData() ? "aria2.removeDownloadResult" : "aria2.remove", makeRequest(log,
buildRequest(removeTask.includingData() ? "aria2.removeDownloadResult" : "aria2.remove",
params.put(removeTask.getTargetTorrent().getUniqueID())).toString()); params.put(removeTask.getTargetTorrent().getUniqueID())).toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -177,28 +181,29 @@ public class Aria2Adapter implements IDaemonAdapter {
// Pause a torrent // Pause a torrent
PauseTask pauseTask = (PauseTask) task; PauseTask pauseTask = (PauseTask) task;
makeRequest(buildRequest("aria2.pause", params.put(pauseTask.getTargetTorrent().getUniqueID())) makeRequest(log, buildRequest("aria2.pause", params.put(pauseTask.getTargetTorrent().getUniqueID()))
.toString()); .toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Resume all torrents // Resume all torrents
makeRequest(buildRequest("aria2.pauseAll", null).toString()); makeRequest(log, buildRequest("aria2.pauseAll", null).toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
ResumeTask resumeTask = (ResumeTask) task; ResumeTask resumeTask = (ResumeTask) task;
makeRequest(buildRequest("aria2.unpause", params.put(resumeTask.getTargetTorrent().getUniqueID())) makeRequest(log,
buildRequest("aria2.unpause", params.put(resumeTask.getTargetTorrent().getUniqueID()))
.toString()); .toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeRequest(buildRequest("aria2.unpauseAll", null).toString()); makeRequest(log, buildRequest("aria2.unpauseAll", null).toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTransferRates: case SetTransferRates:
@ -206,12 +211,12 @@ public class Aria2Adapter implements IDaemonAdapter {
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
JSONObject options = new JSONObject(); JSONObject options = new JSONObject();
options.put("max-overall-download-limit", (ratesTask.getDownloadRate() == null ? -1 : ratesTask options.put("max-overall-download-limit",
.getDownloadRate().intValue())); (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()));
options.put("max-overall-upload-limit", (ratesTask.getUploadRate() == null ? -1 : ratesTask options.put("max-overall-upload-limit",
.getUploadRate().intValue())); (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate()));
makeRequest(buildRequest("aria2.changeGlobalOption", params.put(options)).toString()); makeRequest(log, buildRequest("aria2.changeGlobalOption", params.put(options)).toString());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
@ -252,27 +257,27 @@ public class Aria2Adapter implements IDaemonAdapter {
} }
private synchronized JSONObject makeRequest(String data) throws DaemonException { private synchronized JSONObject makeRequest(Log log, String data) throws DaemonException {
String raw = makeRawRequest(data); String raw = makeRawRequest(log, data);
try { try {
return new JSONObject(raw); return new JSONObject(raw);
} catch (JSONException e) { } catch (JSONException e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString()); throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString());
} }
} }
private synchronized JSONArray makeRequestForArray(String data) throws DaemonException { private synchronized JSONArray makeRequestForArray(Log log, String data) throws DaemonException {
String raw = makeRawRequest(data); String raw = makeRawRequest(log, data);
try { try {
return new JSONArray(raw); return new JSONArray(raw);
} catch (JSONException e) { } catch (JSONException e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString()); throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString());
} }
} }
private synchronized String makeRawRequest(String data) throws DaemonException { private synchronized String makeRawRequest(Log log, String data) throws DaemonException {
try { try {
@ -284,8 +289,9 @@ public class Aria2Adapter implements IDaemonAdapter {
} }
// Set POST URL and data // Set POST URL and data
String url = (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" String url =
+ settings.getPort() + (settings.getFolder() == null ? "" : settings.getFolder()) + "/jsonrpc"; (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
(settings.getFolder() == null ? "" : settings.getFolder()) + "/jsonrpc";
HttpPost httppost = new HttpPost(url); HttpPost httppost = new HttpPost(url);
httppost.setEntity(new StringEntity(data)); httppost.setEntity(new StringEntity(data));
httppost.setHeader("Content-Type", "application/json"); httppost.setHeader("Content-Type", "application/json");
@ -295,22 +301,22 @@ public class Aria2Adapter implements IDaemonAdapter {
HttpResponse response = httpclient.execute(httppost); HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
if (entity == null) if (entity == null) {
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity in response object."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity in response object.");
}
// Read JSON response // Read JSON response
InputStream instream = entity.getContent(); InputStream instream = entity.getContent();
String result = HttpHelper.convertStreamToString(instream); String result = HttpHelper.convertStreamToString(instream);
instream.close(); instream.close();
DLog.d(LOG_NAME, log.d(LOG_NAME, "Success: " +
"Success: " (result.length() > 300 ? result.substring(0, 300) + "... (" + result.length() + " chars)" :
+ (result.length() > 300 ? result.substring(0, 300) + "... (" + result.length() + " chars)" result));
: result));
return result; return result;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -332,23 +338,26 @@ public class Aria2Adapter implements IDaemonAdapter {
int errorCode = tor.optInt("errorCode", 0); int errorCode = tor.optInt("errorCode", 0);
String error = errorCode > 0 ? convertAriaError(errorCode) : null; String error = errorCode > 0 ? convertAriaError(errorCode) : null;
String name = null; String name = null;
JSONObject bittorrent = null; JSONObject bittorrent;
if (tor.has("bittorrent")) { if (tor.has("bittorrent")) {
// Get name form the bittorrent info object // Get name form the bittorrent info object
bittorrent = tor.getJSONObject("bittorrent"); bittorrent = tor.getJSONObject("bittorrent");
if (bittorrent.has("info")) if (bittorrent.has("info")) {
name = bittorrent.getJSONObject("info").getString("name"); name = bittorrent.getJSONObject("info").getString("name");
}
} else if (tor.has("files")) { } else if (tor.has("files")) {
// Get name from the first included file we can find // Get name from the first included file we can find
JSONArray files = tor.getJSONArray("files"); JSONArray files = tor.getJSONArray("files");
if (files.length() > 0) { if (files.length() > 0) {
name = Uri.parse(files.getJSONObject(0).getString("path")).getLastPathSegment(); name = Uri.parse(files.getJSONObject(0).getString("path")).getLastPathSegment();
if (name == null) if (name == null) {
name = files.getJSONObject(0).getString("path"); name = files.getJSONObject(0).getString("path");
} }
} }
if (name == null) }
if (name == null) {
name = tor.getString("gid"); // Fallback name name = tor.getString("gid"); // Fallback name
}
// @formatter:off // @formatter:off
torrents.add(new Torrent( torrents.add(new Torrent(
j, j,
@ -388,11 +397,11 @@ public class Aria2Adapter implements IDaemonAdapter {
JSONObject file = response.getJSONObject(j); JSONObject file = response.getJSONObject(j);
// Add the parsed torrent to the list // Add the parsed torrent to the list
// @formatter:off
String rel = file.getString("path"); String rel = file.getString("path");
if (rel.startsWith(torrent.getLocationDir())) { if (rel.startsWith(torrent.getLocationDir())) {
rel = rel.substring(torrent.getLocationDir().length()); rel = rel.substring(torrent.getLocationDir().length());
} }
// @formatter:off
files.add(new TorrentFile( files.add(new TorrentFile(
Integer.toString(file.getInt("index")), Integer.toString(file.getInt("index")),
rel, rel,
@ -415,8 +424,9 @@ public class Aria2Adapter implements IDaemonAdapter {
List<String> errors = new ArrayList<String>(); List<String> errors = new ArrayList<String>();
int error = response.optInt("errorCode", 0); int error = response.optInt("errorCode", 0);
if (error > 0) if (error > 0) {
errors.add(convertAriaError(error)); errors.add(convertAriaError(error));
}
if (response.has("bittorrent")) { if (response.has("bittorrent")) {
JSONObject bittorrent = response.getJSONObject("bittorrent"); JSONObject bittorrent = response.getJSONObject("bittorrent");

311
app/src/main/java/org/transdroid/daemon/BitComet/BitCometAdapter.java

@ -17,16 +17,10 @@
*/ */
package org.transdroid.daemon.BitComet; package org.transdroid.daemon.BitComet;
import java.io.File; import com.android.internalcopy.http.multipart.BitCometFilePart;
import java.io.FileNotFoundException; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.io.StringReader; import com.android.internalcopy.http.multipart.Part;
import java.io.UnsupportedEncodingException; import com.android.internalcopy.http.multipart.Utf8StringPart;
import java.net.URI;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -38,18 +32,19 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HTTP;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus; import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask; import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask; import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult; import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskResult;
@ -60,26 +55,28 @@ import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.Part;
import com.android.internalcopy.http.multipart.MultipartEntity;
import com.android.internalcopy.http.multipart.BitCometFilePart;
import com.android.internalcopy.http.multipart.Utf8StringPart;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlPullParserFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/** /**
* The daemon adapter for the BitComet torrent client. * The daemon adapter for the BitComet torrent client.
*
* @author SeNS (sensboston) * @author SeNS (sensboston)
* * <p/>
* 09/26/2012: added AJAX support for BitComet v.1.34 and up * 09/26/2012: added AJAX support for BitComet v.1.34 and up : added additional tasks support
* : added additional tasks support
*/ */
public class BitCometAdapter implements IDaemonAdapter { public class BitCometAdapter implements IDaemonAdapter {
@ -92,8 +89,38 @@ public class BitCometAdapter implements IDaemonAdapter {
this.settings = settings; this.settings = settings;
} }
/**
* Returns the size of the torrent, as parsed form some string
* @param size The size in a string format, i.e. '691 MB'
* @return The size in bytes
*/
private static long convertSize(String size) {
try {
if (size.endsWith("GB")) {
return (long) (Float.parseFloat(size.substring(0, size.indexOf("GB"))) * 1024 * 1024 * 1024);
} else if (size.endsWith("MB")) {
return (long) (Float.parseFloat(size.substring(0, size.indexOf("MB"))) * 1024 * 1024);
} else if (size.endsWith("kB")) {
return (long) (Float.parseFloat(size.substring(0, size.indexOf("kB"))) * 1024);
} else if (size.endsWith("B")) {
return (long) (Float.parseFloat(size.substring(0, size.indexOf("B"))));
}
} catch (Exception e) {
}
return 0;
}
/**
* Returns the part done (or progress) of a torrent, as parsed from some string
* @param progress The part done in a string format, i.e. '15.96'
* @return The part done as [0..1] fraction, i.e. 0.1596
*/
public static float convertProgress(String progress) {
return Float.parseFloat(progress) / 1000.0f;
}
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
@ -102,89 +129,97 @@ public class BitCometAdapter implements IDaemonAdapter {
// Request all torrents from server // Request all torrents from server
// first, check client for the new AJAX interface (BitComet v.1.34 and up) // first, check client for the new AJAX interface (BitComet v.1.34 and up)
try { try {
String xmlResult = makeRequest("/panel/task_list_xml"); String xmlResult = makeRequest(log, "/panel/task_list_xml");
if (xmlResult.startsWith("<?xml", 0)) { if (xmlResult.startsWith("<?xml", 0)) {
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseXmlTorrents(xmlResult), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseXmlTorrents(xmlResult),
null);
} }
} catch (Exception e) { } catch (Exception e) {
// it's probably an old client, parse HTML instead // it's probably an old client, parse HTML instead
String htmlResult = makeRequest("/panel/task_list"); String htmlResult = makeRequest(log, "/panel/task_list");
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseHttpTorrents(htmlResult), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseHttpTorrents(log, htmlResult),
null);
} }
case GetFileList: case GetFileList:
// Request files listing for a specific torrent // Request files listing for a specific torrent
String fhash = ((GetFileListTask)task).getTargetTorrent().getUniqueID(); String fhash = task.getTargetTorrent().getUniqueID();
String fileListResult = makeRequest("/panel/task_detail", new BasicNameValuePair("id", fhash), String fileListResult = makeRequest(log, "/panel/task_detail", new BasicNameValuePair("id", fhash),
new BasicNameValuePair("show", "files")); new BasicNameValuePair("show", "files"));
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseHttpTorrentFiles(fileListResult, return new GetFileListTaskSuccessResult((GetFileListTask) task,
fhash)); parseHttpTorrentFiles(fileListResult, fhash));
case AddByFile: case AddByFile:
// Upload a local .torrent file // Upload a local .torrent file
String ufile = ((AddByFileTask)task).getFile(); String ufile = ((AddByFileTask) task).getFile();
makeFileUploadRequest("/panel/task_add_bt_result", ufile); makeFileUploadRequest(log, "/panel/task_add_bt_result", ufile);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask)task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
makeUploadUrlRequest("/panel/task_add_httpftp_result", url); makeUploadUrlRequest(log, "/panel/task_add_httpftp_result", url);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String magnetUrl = ((AddByMagnetUrlTask)task).getUrl(); String magnetUrl = ((AddByMagnetUrlTask) task).getUrl();
makeUploadUrlRequest("/panel/task_add_httpftp_result", magnetUrl); makeUploadUrlRequest(log, "/panel/task_add_httpftp_result", magnetUrl);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeRequest("/panel/task_delete", new BasicNameValuePair("id", removeTask.getTargetTorrent().getUniqueID()), makeRequest(log, "/panel/task_delete",
new BasicNameValuePair("action", (removeTask.includingData()? "delete_all": "delete_task"))); new BasicNameValuePair("id", removeTask.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("action",
(removeTask.includingData() ? "delete_all" : "delete_task")));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeRequest("/panel/task_action", new BasicNameValuePair("id", task.getTargetTorrent().getUniqueID()), new BasicNameValuePair("action", "stop")); makeRequest(log, "/panel/task_action",
new BasicNameValuePair("id", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("action", "stop"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeRequest("/panel/task_action", new BasicNameValuePair("id", task.getTargetTorrent().getUniqueID()), new BasicNameValuePair("action", "start")); makeRequest(log, "/panel/task_action",
new BasicNameValuePair("id", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("action", "start"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Suspend (pause) all active torrents // Suspend (pause) all active torrents
makeRequest("/panel/tasklist_action", new BasicNameValuePair("id", "suspend_all")); makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "suspend_all"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume suspended torrents // Resume suspended torrents
makeRequest("/panel/tasklist_action", new BasicNameValuePair("id", "resume_all")); makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "resume_all"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case StopAll: case StopAll:
// Stop all torrents // Stop all torrents
makeRequest("/panel/tasklist_action", new BasicNameValuePair("id", "stop_all")); makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "stop_all"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case StartAll: case StartAll:
// Start all torrents for download and seeding // Start all torrents for download and seeding
makeRequest("/panel/tasklist_action", new BasicNameValuePair("id", "start_all_download")); makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "start_all_download"));
makeRequest("/panel/tasklist_action", new BasicNameValuePair("id", "start_all_seeding")); makeRequest(log, "/panel/tasklist_action", new BasicNameValuePair("id", "start_all_seeding"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -192,28 +227,29 @@ public class BitCometAdapter implements IDaemonAdapter {
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
String dl = Integer.toString((ratesTask.getDownloadRate() == null? -1: ratesTask.getDownloadRate().intValue())); String dl =
String ul = Integer.toString((ratesTask.getUploadRate() == null? -1: ratesTask.getUploadRate().intValue())); Integer.toString((ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()));
makeRequest("/panel/option_set", new BasicNameValuePair("key", "down_rate_max"), new BasicNameValuePair("value", dl)); String ul = Integer.toString((ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate()));
makeRequest("/panel/option_set", new BasicNameValuePair("key", "up_rate_max"), new BasicNameValuePair("value", ul)); makeRequest(log, "/panel/option_set", new BasicNameValuePair("key", "down_rate_max"),
new BasicNameValuePair("value", dl));
makeRequest(log, "/panel/option_set", new BasicNameValuePair("key", "up_rate_max"),
new BasicNameValuePair("value", ul));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
} task.getMethod() + " is not supported by " + getType()));
} }
catch (DaemonException e) { } catch (DaemonException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
} }
} }
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all Buffalo NAS requests. * Instantiates an HTTP client with proper credentials that can be used for all Buffalo NAS requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise(int connectionTimeout) throws DaemonException { private void initialise() throws DaemonException {
httpclient = HttpHelper.createStandardHttpClient(settings, true); httpclient = HttpHelper.createStandardHttpClient(settings, true);
} }
@ -225,13 +261,13 @@ public class BitCometAdapter implements IDaemonAdapter {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + path; return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + path;
} }
private String makeRequest(String url, NameValuePair... params) throws DaemonException { private String makeRequest(Log log, String url, NameValuePair... params) throws DaemonException {
try { try {
// Initialize the HTTP client // Initialize the HTTP client
if (httpclient == null) { if (httpclient == null) {
initialise(HttpHelper.DEFAULT_CONNECTION_TIMEOUT); initialise();
} }
// Add the parameters to the query string // Add the parameters to the query string
@ -260,25 +296,25 @@ public class BitCometAdapter implements IDaemonAdapter {
return result; return result;
} }
DLog.d(LOG_NAME, "Error: No entity in HTTP response"); log.d(LOG_NAME, "Error: No entity in HTTP response");
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private boolean makeFileUploadRequest(String path, String file) throws DaemonException { private boolean makeFileUploadRequest(Log log, String path, String file) throws DaemonException {
try { try {
// Initialize the HTTP client // Initialize the HTTP client
if (httpclient == null) { if (httpclient == null) {
initialise(HttpHelper.DEFAULT_CONNECTION_TIMEOUT); initialise();
} }
// Get default download file location first // Get default download file location first
@ -291,13 +327,14 @@ public class BitCometAdapter implements IDaemonAdapter {
String result = HttpHelper.convertStreamToString(instream); String result = HttpHelper.convertStreamToString(instream);
instream.close(); instream.close();
int idx = result.indexOf("save_path' value='")+18; int idx = result.indexOf("save_path' value='") + 18;
String defaultPath = result.substring(idx, result.indexOf("'>", idx)); String defaultPath = result.substring(idx, result.indexOf("'>", idx));
// Setup request using POST // Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path)); HttpPost httppost = new HttpPost(buildWebUIUrl(path));
File upload = new File(URI.create(file)); File upload = new File(URI.create(file));
Part[] parts = { new BitCometFilePart("torrent_file", upload), new Utf8StringPart("save_path", defaultPath) }; Part[] parts =
{new BitCometFilePart("torrent_file", upload), new Utf8StringPart("save_path", defaultPath)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
// Make the request // Make the request
@ -309,7 +346,9 @@ public class BitCometAdapter implements IDaemonAdapter {
instream = entity.getContent(); instream = entity.getContent();
result = HttpHelper.convertStreamToString(instream); result = HttpHelper.convertStreamToString(instream);
instream.close(); instream.close();
if (result.indexOf("failed!") > 0) throw new Exception("Adding torrent file failed"); if (result.indexOf("failed!") > 0) {
throw new Exception("Adding torrent file failed");
}
} }
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
@ -319,18 +358,18 @@ public class BitCometAdapter implements IDaemonAdapter {
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new DaemonException(ExceptionType.FileAccessError, e.toString()); throw new DaemonException(ExceptionType.FileAccessError, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private boolean makeUploadUrlRequest(String path, String url) throws DaemonException { private boolean makeUploadUrlRequest(Log log, String path, String url) throws DaemonException {
try { try {
// Initialize the HTTP client // Initialize the HTTP client
if (httpclient == null) { if (httpclient == null) {
initialise(HttpHelper.DEFAULT_CONNECTION_TIMEOUT); initialise();
} }
// Get default download file location first // Get default download file location first
@ -343,7 +382,7 @@ public class BitCometAdapter implements IDaemonAdapter {
String result = HttpHelper.convertStreamToString(instream); String result = HttpHelper.convertStreamToString(instream);
instream.close(); instream.close();
int idx = result.indexOf("save_path' value='")+18; int idx = result.indexOf("save_path' value='") + 18;
String defaultPath = result.substring(idx, result.indexOf("'>", idx)); String defaultPath = result.substring(idx, result.indexOf("'>", idx));
// Setup form fields and post request // Setup form fields and post request
@ -375,34 +414,36 @@ public class BitCometAdapter implements IDaemonAdapter {
} }
return false; return false;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
/** /**
* Parse BitComet HTML page (http response) * Parse BitComet HTML page (http response)
* @param response * @param response The raw HTML response from the server
* @return * @return The parsed list of torrents from the raw HTML content
* @throws DaemonException * @throws DaemonException
*/ */
private ArrayList<Torrent> parseHttpTorrents(String response) throws DaemonException { private ArrayList<Torrent> parseHttpTorrents(Log log, String response) throws DaemonException {
ArrayList<Torrent> torrents = new ArrayList<Torrent>(); ArrayList<Torrent> torrents = new ArrayList<Torrent>();
try { try {
// Find, prepare and split substring with HTML tag TABLE // Find, prepare and split substring with HTML tag TABLE
String[] parts = response.substring(response.indexOf("<TABLE"),response.indexOf("</TABLE>")).replaceAll("</td>", "").replaceAll("</tr>", "").replaceAll("\n", "").split("<tr>"); String[] parts =
response.substring(response.indexOf("<TABLE"), response.indexOf("</TABLE>")).replaceAll("</td>", "")
.replaceAll("</tr>", "").replaceAll("\n", "").split("<tr>");
for (int i=2; i<parts.length; i++) { for (int i = 2; i < parts.length; i++) {
String[] subParts = parts[i].replaceAll("<td>", "<td").split("<td"); String[] subParts = parts[i].replaceAll("<td>", "<td").split("<td");
if (subParts.length == 10 && subParts[1].contains("BT") ) { if (subParts.length == 10 && subParts[1].contains("BT")) {
String name = subParts[2].substring(subParts[2].indexOf("/panel/task_detail")); String name = subParts[2].substring(subParts[2].indexOf("/panel/task_detail"));
name = name.substring(name.indexOf(">")+1, name.indexOf("<")); name = name.substring(name.indexOf(">") + 1, name.indexOf("<"));
TorrentStatus status = convertStatus(subParts[3]); TorrentStatus status = convertStatus(subParts[3]);
String percenDoneStr = subParts[6]; String percenDoneStr = subParts[6];
@ -411,10 +452,11 @@ public class BitCometAdapter implements IDaemonAdapter {
long size = convertSize(subParts[5]); long size = convertSize(subParts[5]);
float percentDone = Float.parseFloat(percenDoneStr.substring(0, percenDoneStr.indexOf("%"))); float percentDone = Float.parseFloat(percenDoneStr.substring(0, percenDoneStr.indexOf("%")));
long sizeDone = (long) (size * percentDone / 100 ); long sizeDone = (long) (size * percentDone / 100);
int rateUp = 1000 * Integer.parseInt(uploadRateStr.substring(0, uploadRateStr.indexOf("kB/s"))); int rateUp = 1000 * Integer.parseInt(uploadRateStr.substring(0, uploadRateStr.indexOf("kB/s")));
int rateDown = 1000 * Integer.parseInt(downloadRateStr.substring(0, downloadRateStr.indexOf("kB/s"))); int rateDown =
1000 * Integer.parseInt(downloadRateStr.substring(0, downloadRateStr.indexOf("kB/s")));
// Unfortunately, there is no info for above values providing by BitComet now, // Unfortunately, there is no info for above values providing by BitComet now,
// so we may only send additional request for that // so we may only send additional request for that
@ -423,42 +465,49 @@ public class BitCometAdapter implements IDaemonAdapter {
int knownLeechers = 0; int knownLeechers = 0;
int knownSeeders = 0; int knownSeeders = 0;
int distributed_copies = 0; int distributed_copies = 0;
long sizeUp = 0; long sizeUp;
String comment = ""; String comment;
Date dateAdded = new Date(); Date dateAdded;
// Comment code below to speedup torrent listing // Comment code below to speedup torrent listing
// P.S. feature request to extend torrents info is already sent to the BitComet developers // P.S. feature request to extend torrents info is already sent to the BitComet developers
//* //*
try {
// Lets make summary request and parse details // Lets make summary request and parse details
String summary = makeRequest("/panel/task_detail", new BasicNameValuePair("id", ""+(i-2)), new BasicNameValuePair("show", "summary")); String summary = makeRequest(log, "/panel/task_detail", new BasicNameValuePair("id", "" + (i - 2)),
new BasicNameValuePair("show", "summary"));
String[] sumParts = summary.substring(summary.indexOf("<div align=\"left\">Value</div></th>")).split("<tr><td>"); String[] sumParts = summary.substring(summary.indexOf("<div align=\"left\">Value</div></th>"))
comment = sumParts[7].substring(sumParts[7].indexOf("<td>")+4, sumParts[7].indexOf("</td></tr>")); .split("<tr><td>");
comment = sumParts[7].substring(sumParts[7].indexOf("<td>") + 4, sumParts[7].indexOf("</td></tr>"));
// Indexes for date and uploaded size // Indexes for date and uploaded size
int idx = 9; int idx = 9;
int sizeIdx = 12; int sizeIdx = 12;
if (status == TorrentStatus.Downloading) { if (status == TorrentStatus.Downloading) {
seeders = Integer.parseInt(sumParts[9].substring(sumParts[9].indexOf("Seeds:")+6, sumParts[9].indexOf("(Max possible"))); seeders = Integer.parseInt(sumParts[9]
leechers = Integer.parseInt(sumParts[9].substring(sumParts[9].indexOf("Peers:")+6, sumParts[9].lastIndexOf("(Max possible"))); .substring(sumParts[9].indexOf("Seeds:") + 6, sumParts[9].indexOf("(Max possible")));
knownSeeders = Integer.parseInt(sumParts[9].substring(sumParts[9].indexOf("(Max possible:")+14, sumParts[9].indexOf(")"))); leechers = Integer.parseInt(sumParts[9].substring(sumParts[9].indexOf("Peers:") + 6,
knownLeechers = Integer.parseInt(sumParts[9].substring(sumParts[9].lastIndexOf("(Max possible:")+14, sumParts[9].lastIndexOf(")"))); sumParts[9].lastIndexOf("(Max possible")));
knownSeeders = Integer.parseInt(sumParts[9]
.substring(sumParts[9].indexOf("(Max possible:") + 14, sumParts[9].indexOf(")")));
knownLeechers = Integer.parseInt(sumParts[9]
.substring(sumParts[9].lastIndexOf("(Max possible:") + 14,
sumParts[9].lastIndexOf(")")));
idx = 13; idx = 13;
sizeIdx = 16; sizeIdx = 16;
} }
DateFormat df = new SimpleDateFormat("yyyy-mm-dd kk:mm:ss"); DateFormat df = new SimpleDateFormat("yyyy-mm-dd kk:mm:ss");
dateAdded = df.parse(sumParts[idx].substring(sumParts[idx].indexOf("<td>")+4, sumParts[idx].indexOf("</td></tr>"))); dateAdded = df.parse(sumParts[idx]
.substring(sumParts[idx].indexOf("<td>") + 4, sumParts[idx].indexOf("</td></tr>")));
//sizeDone = convertSize(sumParts[sizeIdx].substring(sumParts[sizeIdx].indexOf("<td>")+4, sumParts[sizeIdx].indexOf(" ("))); //sizeDone = convertSize(sumParts[sizeIdx].substring(sumParts[sizeIdx].indexOf("<td>")+4, sumParts[sizeIdx].indexOf(" (")));
sizeUp = convertSize(sumParts[sizeIdx+1].substring(sumParts[sizeIdx+1].indexOf("<td>")+4, sumParts[sizeIdx+1].indexOf(" ("))); sizeUp = convertSize(sumParts[sizeIdx + 1]
} .substring(sumParts[sizeIdx + 1].indexOf("<td>") + 4, sumParts[sizeIdx + 1].indexOf(" (")));
catch (Exception e) {}
//* //*
// Add the parsed torrent to the list // Add the parsed torrent to the list
// @formatter:off
torrents.add(new Torrent( torrents.add(new Torrent(
(long)i-2, (long)i-2,
null, null,
@ -482,10 +531,10 @@ public class BitCometAdapter implements IDaemonAdapter {
null, null,
null, null,
settings.getType())); settings.getType()));
// @formatter:on
} }
} }
} } catch (Exception e) {
catch (Exception e) {
throw new DaemonException(ExceptionType.UnexpectedResponse, "Invalid BitComet HTTP response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "Invalid BitComet HTTP response.");
} }
@ -493,10 +542,9 @@ public class BitCometAdapter implements IDaemonAdapter {
} }
/** /**
* Parse BitComet AJAX response * Parse BitComet AJAX response that code was copy-pasted and slightly modified from \Ktorrent\StatsParser.java
* that code was copy-pasted and slightly modified from \Ktorrent\StatsParser.java * @param response The raw XML data as string that was returned by the server
* @param response * @return The parsed list of torrents from the XML
* @return
* @throws DaemonException * @throws DaemonException
*/ */
private ArrayList<Torrent> parseXmlTorrents(String response) throws DaemonException { private ArrayList<Torrent> parseXmlTorrents(String response) throws DaemonException {
@ -511,8 +559,7 @@ public class BitCometAdapter implements IDaemonAdapter {
// Temp variables to load into torrent objects // Temp variables to load into torrent objects
int id = 0; int id = 0;
String name = ""; String name = "";
@SuppressWarnings("unused") @SuppressWarnings("unused") String hash = "";
String hash = "";
TorrentStatus status = TorrentStatus.Unknown; TorrentStatus status = TorrentStatus.Unknown;
long sizeDone = 0; long sizeDone = 0;
long sizeUp = 0; long sizeUp = 0;
@ -537,6 +584,7 @@ public class BitCometAdapter implements IDaemonAdapter {
// End of a 'transfer' item, add gathered torrent data // End of a 'transfer' item, add gathered torrent data
sizeDone = (long) (totalSize * progress); sizeDone = (long) (totalSize * progress);
// @formatter:off
torrents.add(new Torrent( torrents.add(new Torrent(
id, id,
null, // hash, // we suppose to use simple integer IDs null, // hash, // we suppose to use simple integer IDs
@ -560,10 +608,11 @@ public class BitCometAdapter implements IDaemonAdapter {
null, null,
null, // Not supported in the web interface null, // Not supported in the web interface
settings.getType())); settings.getType()));
// @formatter:on
id++; // Stop/start/etc. requests are made by ID, which is the order number in the returned XML list :-S id++; // Stop/start/etc. requests are made by ID, which is the order number in the returned XML list :-S
} else if (next == XmlPullParser.START_TAG && tagName.equals("task")){ } else if (next == XmlPullParser.START_TAG && tagName.equals("task")) {
// Start of a new 'transfer' item; reset gathered torrent data // Start of a new 'transfer' item; reset gathered torrent data
name = ""; name = "";
@ -582,7 +631,7 @@ public class BitCometAdapter implements IDaemonAdapter {
label = ""; label = "";
dateAdded = new Date(); dateAdded = new Date();
} else if (next == XmlPullParser.START_TAG){ } else if (next == XmlPullParser.START_TAG) {
// Probably encountered a torrent property, i.e. '<type>BT</type>' // Probably encountered a torrent property, i.e. '<type>BT</type>'
next = xpp.next(); next = xpp.next();
@ -638,8 +687,8 @@ public class BitCometAdapter implements IDaemonAdapter {
/** /**
* Parse BitComet HTML page (HTTP response) * Parse BitComet HTML page (HTTP response)
* @param response * @param response The raw HTML response from the server
* @return * @return The parsed list of files in the torrent from the raw HTML
* @throws DaemonException * @throws DaemonException
*/ */
private ArrayList<TorrentFile> parseHttpTorrentFiles(String response, String hash) throws DaemonException { private ArrayList<TorrentFile> parseHttpTorrentFiles(String response, String hash) throws DaemonException {
@ -649,19 +698,21 @@ public class BitCometAdapter implements IDaemonAdapter {
try { try {
String[] files = response.substring(response.indexOf("Operation Method</div></th>")+27, response.lastIndexOf("</TABLE>")).replaceAll("</td>", "").replaceAll("</tr>", "").split("<tr>"); String[] files = response.substring(response.indexOf("Operation Method</div></th>") + 27,
response.lastIndexOf("</TABLE>")).replaceAll("</td>", "").replaceAll("</tr>", "").split("<tr>");
for (int i = 1; i < files.length; i++) { for (int i = 1; i < files.length; i++) {
String[] fileDetails = files[i].replace(">","").split("<td"); String[] fileDetails = files[i].replace(">", "").split("<td");
long size = convertSize(fileDetails[4].substring(fileDetails[4].indexOf("&nbsp&nbsp ")+11)); long size = convertSize(fileDetails[4].substring(fileDetails[4].indexOf("&nbsp&nbsp ") + 11));
long sizeDone = 0; long sizeDone = 0;
if (!fileDetails[2].contains("--")) { if (!fileDetails[2].contains("--")) {
double percentDone = Double.parseDouble(fileDetails[2].substring(0, fileDetails[2].indexOf("%"))); double percentDone = Double.parseDouble(fileDetails[2].substring(0, fileDetails[2].indexOf("%")));
sizeDone = (long) ( size / 100.0 * percentDone); sizeDone = (long) (size / 100.0 * percentDone);
} }
// @formatter:off
torrentfiles.add(new TorrentFile( torrentfiles.add(new TorrentFile(
hash, hash,
fileDetails[3], fileDetails[3],
@ -670,9 +721,9 @@ public class BitCometAdapter implements IDaemonAdapter {
size, size,
sizeDone, sizeDone,
convertPriority(fileDetails[1]))); convertPriority(fileDetails[1])));
// @formatter:on
} }
} } catch (Exception e) {
catch (Exception e) {
throw new DaemonException(ExceptionType.UnexpectedResponse, "Invalid BitComet HTTP response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "Invalid BitComet HTTP response.");
} }
@ -680,30 +731,9 @@ public class BitCometAdapter implements IDaemonAdapter {
return torrentfiles; return torrentfiles;
} }
/**
* Returns the size of the torrent, as parsed form some string
* @param size The size in a string format, i.e. '691 MB'
* @return The size in bytes
*/
private static long convertSize(String size) {
try {
if (size.endsWith("GB")) {
return (long)(Float.parseFloat(size.substring(0, size.indexOf("GB"))) * 1024 * 1024 * 1024);
} else if (size.endsWith("MB")) {
return (long)(Float.parseFloat(size.substring(0, size.indexOf("MB"))) * 1024 * 1024);
} else if (size.endsWith("kB")) {
return (long)(Float.parseFloat(size.substring(0, size.indexOf("kB"))) * 1024);
} else if (size.endsWith("B")) {
return (long)(Float.parseFloat(size.substring(0, size.indexOf("B"))));
}
}
catch (Exception e) { }
return 0;
}
/** /**
* Parse BitComet torrent files priority * Parse BitComet torrent files priority
**/ */
private Priority convertPriority(String priority) { private Priority convertPriority(String priority) {
if (priority.equals("Very High") || priority.equals("High")) { if (priority.equals("Very High") || priority.equals("High")) {
return Priority.High; return Priority.High;
@ -715,7 +745,7 @@ public class BitCometAdapter implements IDaemonAdapter {
/** /**
* Parse BitComet torrent status * Parse BitComet torrent status
**/ */
private TorrentStatus convertStatus(String state) { private TorrentStatus convertStatus(String state) {
// Status is given as a descriptive string and an indication if the torrent was stopped/paused // Status is given as a descriptive string and an indication if the torrent was stopped/paused
if (state.equals("stopped")) { if (state.equals("stopped")) {
@ -726,15 +756,6 @@ public class BitCometAdapter implements IDaemonAdapter {
return TorrentStatus.Unknown; return TorrentStatus.Unknown;
} }
/**
* Returns the part done (or progress) of a torrent, as parsed from some string
* @param progress The part done in a string format, i.e. '15.96'
* @return The part done as [0..1] fraction, i.e. 0.1596
*/
public static float convertProgress(String progress) {
return Float.parseFloat(progress) / 1000.0f;
}
@Override @Override
public Daemon getType() { public Daemon getType() {
return settings.getType(); return settings.getType();

49
app/src/main/java/org/transdroid/daemon/Bitflu/BitfluAdapter.java

@ -17,26 +17,22 @@
*/ */
package org.transdroid.daemon.Bitflu; package org.transdroid.daemon.Bitflu;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus; import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByMagnetUrlTask; import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask; import org.transdroid.daemon.task.DaemonTask;
@ -51,7 +47,11 @@ import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import org.transdroid.daemon.util.DLog;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
/** /**
* An adapter that allows for easy access to uTorrent torrent data. Communication is handled via authenticated JSON-RPC * An adapter that allows for easy access to uTorrent torrent data. Communication is handled via authenticated JSON-RPC
@ -84,22 +84,22 @@ public class BitfluAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server // Request all torrents from server
JSONObject result = makeBitfluRequest(RPC_TORRENT_LIST); JSONObject result = makeBitfluRequest(log, RPC_TORRENT_LIST);
return new RetrieveTaskSuccessResult((RetrieveTask) task, return new RetrieveTaskSuccessResult((RetrieveTask) task,
parseJsonRetrieveTorrents(result.getJSONArray(JSON_ROOT)), null); parseJsonRetrieveTorrents(result.getJSONArray(JSON_ROOT)), null);
case GetStats: case GetStats:
return new GetStatsTaskSuccessResult((GetStatsTask) task, false, -1); return new GetStatsTaskSuccessResult((GetStatsTask) task, false, -1);
case Pause: case Pause:
makeBitfluRequest(RPC_PAUSE_TORRENT + task.getTargetTorrent().getUniqueID()); makeBitfluRequest(log, RPC_PAUSE_TORRENT + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
makeBitfluRequest(RPC_RESUME_TORRENT + task.getTargetTorrent().getUniqueID()); makeBitfluRequest(log, RPC_RESUME_TORRENT + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
@ -109,20 +109,20 @@ public class BitfluAdapter implements IDaemonAdapter {
if (removeTask.includingData()) { if (removeTask.includingData()) {
removeUriBase = RPC_REMOVE_TORRENT; removeUriBase = RPC_REMOVE_TORRENT;
} }
DLog.d(LOG_NAME, "*** CALLING " + removeUriBase); makeBitfluRequest(log, removeUriBase + task.getTargetTorrent().getUniqueID());
makeBitfluRequest(removeUriBase + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case GetFileList: case GetFileList:
JSONObject jfiles = makeBitfluRequest(RPC_TORRENT_FILES + task.getTargetTorrent().getUniqueID()); JSONObject jfiles =
makeBitfluRequest(log, RPC_TORRENT_FILES + task.getTargetTorrent().getUniqueID());
return new GetFileListTaskSuccessResult((GetFileListTask) task, return new GetFileListTaskSuccessResult((GetFileListTask) task,
parseJsonShowFilesTorrent(jfiles.getJSONArray(JSON_ROOT))); parseJsonShowFilesTorrent(jfiles.getJSONArray(JSON_ROOT)));
case AddByUrl: case AddByUrl:
String url = URLEncoder.encode(((AddByUrlTask) task).getUrl(), "UTF-8"); String url = URLEncoder.encode(((AddByUrlTask) task).getUrl(), "UTF-8");
makeBitfluRequest(RPC_START_DOWNLOAD + url); makeBitfluRequest(log, RPC_START_DOWNLOAD + url);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
String magnet = URLEncoder.encode(((AddByMagnetUrlTask) task).getUrl(), "UTF-8"); String magnet = URLEncoder.encode(((AddByMagnetUrlTask) task).getUrl(), "UTF-8");
makeBitfluRequest(RPC_START_DOWNLOAD + magnet); makeBitfluRequest(log, RPC_START_DOWNLOAD + magnet);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
@ -133,11 +133,12 @@ public class BitfluAdapter implements IDaemonAdapter {
} catch (DaemonException e) { } catch (DaemonException e) {
return new DaemonTaskFailureResult(task, e); return new DaemonTaskFailureResult(task, e);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, e.toString())); return new DaemonTaskFailureResult(task,
new DaemonException(ExceptionType.MethodUnsupported, e.toString()));
} }
} }
private JSONObject makeBitfluRequest(String addToUrl) throws DaemonException { private JSONObject makeBitfluRequest(Log log, String addToUrl) throws DaemonException {
try { try {
@ -174,10 +175,10 @@ public class BitfluAdapter implements IDaemonAdapter {
} catch (DaemonException e) { } catch (DaemonException e) {
throw e; throw e;
} catch (JSONException e) { } catch (JSONException e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -267,7 +268,6 @@ public class BitfluAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all HTTP requests. * Instantiates an HTTP client with proper credentials that can be used for all HTTP requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {
@ -281,10 +281,11 @@ public class BitfluAdapter implements IDaemonAdapter {
*/ */
private String buildWebUIUrl() { private String buildWebUIUrl() {
String webuiroot = ""; String webuiroot = "";
if (settings.getFolder() != null) if (settings.getFolder() != null) {
webuiroot = settings.getFolder(); webuiroot = settings.getFolder();
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() }
+ webuiroot + "/"; return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
webuiroot + "/";
} }
@Override @Override

71
app/src/main/java/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java

@ -17,11 +17,9 @@
*/ */
package org.transdroid.daemon.BuffaloNas; package org.transdroid.daemon.BuffaloNas;
import java.io.File; import com.android.internalcopy.http.multipart.FilePart;
import java.io.FileNotFoundException; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.io.UnsupportedEncodingException; import com.android.internalcopy.http.multipart.Part;
import java.net.URI;
import java.util.ArrayList;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -34,15 +32,16 @@ import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus; import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask; import org.transdroid.daemon.task.DaemonTask;
@ -55,12 +54,13 @@ import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart; import java.io.File;
import com.android.internalcopy.http.multipart.MultipartEntity; import java.io.FileNotFoundException;
import com.android.internalcopy.http.multipart.Part; import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
/** /**
* The daemon adapter for the Buffalo NAS' integrated torrent client. * The daemon adapter for the Buffalo NAS' integrated torrent client.
@ -78,29 +78,29 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server // Request all torrents from server
JSONObject result = new JSONObject(makeRequest("/api/torrents-get")); JSONObject result = new JSONObject(makeRequest(log, "/api/torrents-get"));
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null);
case GetFileList: case GetFileList:
// Request files listing for a specific torrent // Request files listing for a specific torrent
String fhash = ((GetFileListTask) task).getTargetTorrent().getUniqueID(); String fhash = task.getTargetTorrent().getUniqueID();
JSONObject files = new JSONObject(makeRequest("/api/torrent-get-files", new BasicNameValuePair("hash", JSONObject files = new JSONObject(
fhash))); makeRequest(log, "/api/torrent-get-files", new BasicNameValuePair("hash", fhash)));
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files, fhash)); return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files, fhash));
case AddByFile: case AddByFile:
// Upload a local .torrent file // Upload a local .torrent file
String ufile = ((AddByFileTask) task).getFile(); String ufile = ((AddByFileTask) task).getFile();
makeUploadRequest("/api/torrent-add?start=yes", ufile); makeUploadRequest(log, "/api/torrent-add?start=yes", ufile);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
@ -108,7 +108,7 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
// @formatter:off // @formatter:off
makeRequest("/api/torrent-add", makeRequest(log, "/api/torrent-add",
new BasicNameValuePair("url", url), new BasicNameValuePair("url", url),
new BasicNameValuePair("start", "yes")); new BasicNameValuePair("start", "yes"));
// @formatter:on // @formatter:on
@ -119,7 +119,7 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
// @formatter:off // @formatter:off
makeRequest("/api/torrent-remove", makeRequest(log, "/api/torrent-remove",
new BasicNameValuePair("hash", removeTask.getTargetTorrent().getUniqueID()), new BasicNameValuePair("hash", removeTask.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("delete-torrent", "yes"), new BasicNameValuePair("delete-torrent", "yes"),
new BasicNameValuePair("delete-data", (removeTask.includingData() ? "yes" : "no"))); new BasicNameValuePair("delete-data", (removeTask.includingData() ? "yes" : "no")));
@ -129,25 +129,27 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeRequest("/api/torrent-stop", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); makeRequest(log, "/api/torrent-stop",
new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeRequest("/api/torrent-start", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); makeRequest(log, "/api/torrent-start",
new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTransferRates: case SetTransferRates:
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
String dl = Integer.toString((ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate() String dl = Integer.toString(
.intValue() * 1024)); (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate() * 1024));
String ul = Integer.toString((ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate() String ul = Integer.toString(
.intValue() * 1024)); (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate() * 1024));
// @formatter:off // @formatter:off
makeRequest("/api/app-settings-set", makeRequest(log, "/api/app-settings-set",
new BasicNameValuePair("auto_bandwidth_management", "0"), new BasicNameValuePair("auto_bandwidth_management", "0"),
new BasicNameValuePair("max_dl_rate", dl), new BasicNameValuePair("max_dl_rate", dl),
new BasicNameValuePair("max_ul_rate", ul), new BasicNameValuePair("max_ul_rate", ul),
@ -166,13 +168,13 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
} }
} }
private String makeRequest(String url, NameValuePair... params) throws DaemonException { private String makeRequest(Log log, String url, NameValuePair... params) throws DaemonException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
if (httpclient == null) { if (httpclient == null) {
initialise(HttpHelper.DEFAULT_CONNECTION_TIMEOUT); initialise();
} }
// Add the parameters to the query string // Add the parameters to the query string
@ -201,31 +203,31 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
return result; return result;
} }
DLog.d(LOG_NAME, "Error: No entity in HTTP response"); log.d(LOG_NAME, "Error: No entity in HTTP response");
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private boolean makeUploadRequest(String path, String file) throws DaemonException { private boolean makeUploadRequest(Log log, String path, String file) throws DaemonException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
if (httpclient == null) { if (httpclient == null) {
initialise(HttpHelper.DEFAULT_CONNECTION_TIMEOUT); initialise();
} }
// Setup request using POST // Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path)); HttpPost httppost = new HttpPost(buildWebUIUrl(path));
File upload = new File(URI.create(file)); File upload = new File(URI.create(file));
Part[] parts = { new FilePart("fileEl", upload) }; Part[] parts = {new FilePart("fileEl", upload)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
// Make the request // Make the request
@ -235,7 +237,7 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new DaemonException(ExceptionType.FileAccessError, e.toString()); throw new DaemonException(ExceptionType.FileAccessError, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -243,10 +245,9 @@ public class BuffaloNasAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all Buffalo NAS requests. * Instantiates an HTTP client with proper credentials that can be used for all Buffalo NAS requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise(int connectionTimeout) throws DaemonException { private void initialise() throws DaemonException {
httpclient = HttpHelper.createStandardHttpClient(settings, true); httpclient = HttpHelper.createStandardHttpClient(settings, true);

135
app/src/main/java/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java

@ -17,9 +17,9 @@
*/ */
package org.transdroid.daemon.DLinkRouterBT; package org.transdroid.daemon.DLinkRouterBT;
import java.io.File; import com.android.internalcopy.http.multipart.FilePart;
import java.net.URI; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.util.ArrayList; import com.android.internalcopy.http.multipart.Part;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -28,15 +28,16 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus; import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask; import org.transdroid.daemon.task.DaemonTask;
@ -50,17 +51,15 @@ import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.ResumeTask; import org.transdroid.daemon.task.ResumeTask;
import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity; import java.io.File;
import com.android.internalcopy.http.multipart.Part; import java.net.URI;
import java.util.ArrayList;
/** /**
* The daemon adapter for the DLink Router Bittorrent client. * The daemon adapter for the DLink Router Bittorrent client.
*
* @author AvengerMoJo <avengermojo at gmail.com> * @author AvengerMoJo <avengermojo at gmail.com>
*
*/ */
public class DLinkRouterBTAdapter implements IDaemonAdapter { public class DLinkRouterBTAdapter implements IDaemonAdapter {
@ -115,22 +114,22 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server // Request all torrents from server
JSONObject result = makeRequest(API_GET); JSONObject result = makeRequest(log, API_GET);
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonRetrieveTorrents(result),null); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonRetrieveTorrents(result), null);
case GetFileList: case GetFileList:
// Request all details for a specific torrent // Request all details for a specific torrent
JSONObject result2 = makeRequest(API_GET_FILES + task.getTargetTorrent().getUniqueID()); JSONObject result2 = makeRequest(log, API_GET_FILES + task.getTargetTorrent().getUniqueID());
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFileList(result2, task return new GetFileListTaskSuccessResult((GetFileListTask) task,
.getTargetTorrent().getUniqueID())); parseJsonFileList(result2, task.getTargetTorrent().getUniqueID()));
case AddByFile: case AddByFile:
@ -138,22 +137,22 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
String file = ((AddByFileTask) task).getFile(); String file = ((AddByFileTask) task).getFile();
// put .torrent file's data into the request // put .torrent file's data into the request
makeRequest(API_ADD_BY_FILE, new File(URI.create(file))); makeRequest(log, API_ADD_BY_FILE, new File(URI.create(file)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
makeRequest(API_ADD + url); makeRequest(log, API_ADD + url);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeRequest(API_REMOVE + removeTask.getTargetTorrent().getUniqueID() makeRequest(log, API_REMOVE + removeTask.getTargetTorrent().getUniqueID() +
+ (removeTask.includingData() ? API_DEL_DATA + "yes" : ""), false); (removeTask.includingData() ? API_DEL_DATA + "yes" : ""), false);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
// case Stop: // case Stop:
@ -161,7 +160,7 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
// Pause a torrent // Pause a torrent
PauseTask pauseTask = (PauseTask) task; PauseTask pauseTask = (PauseTask) task;
makeRequest(API_STOP + pauseTask.getTargetTorrent().getUniqueID(), false); makeRequest(log, API_STOP + pauseTask.getTargetTorrent().getUniqueID(), false);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
// case PauseAll: // case PauseAll:
@ -176,7 +175,7 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
// Resume a torrent // Resume a torrent
ResumeTask resumeTask = (ResumeTask) task; ResumeTask resumeTask = (ResumeTask) task;
makeRequest(API_START + resumeTask.getTargetTorrent().getUniqueID(), false); makeRequest(log, API_START + resumeTask.getTargetTorrent().getUniqueID(), false);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
// case ResumeAll: // case ResumeAll:
@ -207,9 +206,8 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
// return new DaemonTaskSuccessResult(task); // return new DaemonTaskSuccessResult(task);
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, task return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
.getMethod() task.getMethod() + " is not supported by " + getType()));
+ " is not supported by " + getType()));
} }
} catch (JSONException e) { } catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
@ -218,31 +216,31 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
} }
} }
private JSONObject makeRequest(String requestUrl, File upload) throws DaemonException { private JSONObject makeRequest(Log log, String requestUrl, File upload) throws DaemonException {
return makeRequest(requestUrl, false, upload); return makeRequest(log, requestUrl, false, upload);
} }
private JSONObject makeRequest(String requestUrl) throws DaemonException { private JSONObject makeRequest(Log log, String requestUrl) throws DaemonException {
return makeRequest(requestUrl, true, null); return makeRequest(log, requestUrl, true, null);
} }
private JSONObject makeRequest(String requestUrl, boolean hasRespond) throws DaemonException { private JSONObject makeRequest(Log log, String requestUrl, boolean hasRespond) throws DaemonException {
return makeRequest(requestUrl, hasRespond, null); return makeRequest(log, requestUrl, hasRespond, null);
} }
private JSONObject makeRequest(String requestUrl, boolean hasRespond, File upload) throws DaemonException { private JSONObject makeRequest(Log log, String requestUrl, boolean hasRespond, File upload) throws DaemonException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
if (httpclient == null) { if (httpclient == null) {
initialise(HttpHelper.DEFAULT_CONNECTION_TIMEOUT); initialise();
} }
// Setup request using POST stream with URL and data // Setup request using POST stream with URL and data
HttpPost httppost = new HttpPost(buildWebUIUrl() + requestUrl); HttpPost httppost = new HttpPost(buildWebUIUrl() + requestUrl);
if (upload != null) { if (upload != null) {
Part[] parts = { new FilePart(BT_ADD_BY_FILE, upload) }; Part[] parts = {new FilePart(BT_ADD_BY_FILE, upload)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
} }
@ -264,8 +262,9 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
response = httpclient.execute(httppost); response = httpclient.execute(httppost);
} }
if (!hasRespond) if (!hasRespond) {
return null; return null;
}
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
if (entity != null) { if (entity != null) {
@ -276,24 +275,24 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
JSONObject json = new JSONObject(result); JSONObject json = new JSONObject(result);
instream.close(); instream.close();
DLog.d(LOG_NAME, "Success: " log.d(LOG_NAME, "Success: " +
+ (result.length() > 300 ? result.substring(0, 300) + "... (" + result.length() + " chars)" (result.length() > 300 ? result.substring(0, 300) + "... (" + result.length() + " chars)" :
: result)); result));
// Return the JSON object // Return the JSON object
return json; return json;
} }
DLog.d(LOG_NAME, "Error: No entity in HTTP response"); log.d(LOG_NAME, "Error: No entity in HTTP response");
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");
} catch (DaemonException e) { } catch (DaemonException e) {
throw e; throw e;
} catch (JSONException e) { } catch (JSONException e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString()); throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -301,39 +300,40 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all Transmission requests. * Instantiates an HTTP client with proper credentials that can be used for all Transmission requests.
* * @throws DaemonException On conflicting or missing settings
* @param connectionTimeout
* The connection timeout in milliseconds
* @throws DaemonException
* On conflicting or missing settings
*/ */
private void initialise(int connectionTimeout) throws DaemonException { private void initialise() throws DaemonException {
httpclient = HttpHelper.createStandardHttpClient(settings, true); httpclient = HttpHelper.createStandardHttpClient(settings, true);
} }
/** /**
* Build the URL of the Transmission web UI from the user settings. * Build the URL of the Transmission web UI from the user settings.
*
* @return The URL of the RPC API * @return The URL of the RPC API
*/ */
private String buildWebUIUrl() { private String buildWebUIUrl() {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
+ PATH_TO_API; PATH_TO_API;
} }
private TorrentStatus convertStatus(String state) { private TorrentStatus convertStatus(String state) {
if ("allocating".equals(state)) if ("allocating".equals(state)) {
return TorrentStatus.Checking; return TorrentStatus.Checking;
if ("seeding".equals(state)) }
if ("seeding".equals(state)) {
return TorrentStatus.Seeding; return TorrentStatus.Seeding;
if ("finished".equals(state)) }
if ("finished".equals(state)) {
return TorrentStatus.Downloading; return TorrentStatus.Downloading;
if ("connecting_to_tracker".equals(state)) }
if ("connecting_to_tracker".equals(state)) {
return TorrentStatus.Checking; return TorrentStatus.Checking;
if ("queued_for_checking".equals(state)) }
if ("queued_for_checking".equals(state)) {
return TorrentStatus.Queued; return TorrentStatus.Queued;
if ("downloading".equals(state)) }
if ("downloading".equals(state)) {
return TorrentStatus.Downloading; return TorrentStatus.Downloading;
}
return TorrentStatus.Unknown; return TorrentStatus.Unknown;
} }
@ -345,15 +345,18 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
for (int i = 0; i < rarray.length(); i++) { for (int i = 0; i < rarray.length(); i++) {
JSONObject tor = rarray.getJSONObject(i); JSONObject tor = rarray.getJSONObject(i);
// Add the parsed torrent to the list // Add the parsed torrent to the list
TorrentStatus status = TorrentStatus.Unknown; TorrentStatus status;
if (tor.getInt(BT_STOPPED) == 1) if (tor.getInt(BT_STOPPED) == 1) {
status = TorrentStatus.Paused; status = TorrentStatus.Paused;
else } else {
status = convertStatus(tor.getString(BT_STATE)); status = convertStatus(tor.getString(BT_STATE));
int eta = (int) (tor.getLong(BT_SIZE) / (tor.getInt(BT_DOWNLOAD_RATE) + 1)); }
if (0 > eta) int eta = (int) ((tor.getLong(BT_SIZE) - tor.getLong(BT_DONE)) / (tor.getInt(BT_DOWNLOAD_RATE) + 1));
if (0 > eta) {
eta = -1; eta = -1;
}
// @formatter:off
Torrent new_t = new Torrent( Torrent new_t = new Torrent(
i, i,
tor.getString(BT_HASH), tor.getString(BT_HASH),
@ -366,17 +369,18 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
tor.getInt(BT_PEERS_TOTAL), tor.getInt(BT_PEERS_TOTAL),
tor.getInt(BT_SEEDS_CONNECTED), tor.getInt(BT_SEEDS_CONNECTED),
tor.getInt(BT_SEEDS_TOTAL), tor.getInt(BT_SEEDS_TOTAL),
(int) ((tor.getLong(BT_SIZE) - tor.getLong(BT_DONE)) / (tor.getInt(BT_DOWNLOAD_RATE) + 1)), eta,
tor.getLong(BT_DONE), tor.getLong(BT_DONE),
tor.getLong(BT_PAYLOAD_UPLOAD), tor.getLong(BT_PAYLOAD_UPLOAD),
tor.getLong(BT_SIZE), tor.getLong(BT_SIZE),
(float) (tor.getLong(BT_DONE) / (float) tor.getLong(BT_SIZE)), tor.getLong(BT_DONE) / (float) tor.getLong(BT_SIZE),
Float.parseFloat(tor.getString(BT_COPYS)), Float.parseFloat(tor.getString(BT_COPYS)),
null, null,
null, null,
null, null,
null, null,
settings.getType()); settings.getType());
// @formatter:on
torrents.add(new_t); torrents.add(new_t);
} }
@ -395,15 +399,16 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter {
JSONArray files = jobj.getJSONArray(hash); // "Hash id" JSONArray files = jobj.getJSONArray(hash); // "Hash id"
for (int i = 0; i < files.length(); i++) { for (int i = 0; i < files.length(); i++) {
JSONObject file = files.getJSONObject(i); JSONObject file = files.getJSONObject(i);
// @formatter:off
torrentfiles.add(new TorrentFile( torrentfiles.add(new TorrentFile(
i + "", String.valueOf(i),
// TODO: How is an individual file identified? Index in the array?
file.getString(BT_FILE_NAME), file.getString(BT_FILE_NAME),
file.getString(BT_FILE_NAME), file.getString(BT_FILE_NAME),
null, // Not supported? null, // Not supported?
file.getLong(BT_FILE_SIZE), file.getLong(BT_FILE_SIZE),
file.getLong(BT_FILE_DONE), file.getLong(BT_FILE_DONE),
convertTransmissionPriority(file.getInt(BT_FILE_PRIORITY)))); convertTransmissionPriority(file.getInt(BT_FILE_PRIORITY))));
// @formatter:on
} }
} }

4
app/src/main/java/org/transdroid/daemon/Daemon.java

@ -306,7 +306,7 @@ public enum Daemon {
} }
public static boolean supportsSetTransferRates(Daemon type) { public static boolean supportsSetTransferRates(Daemon type) {
return type == Deluge || type == Transmission || type == uTorrent || type == BitTorrent || type == Deluge || type == rTorrent || type == Vuze || type == BuffaloNas || type == BitComet || type == Aria2 || type == Dummy; return type == Deluge || type == Transmission || type == uTorrent || type == BitTorrent || type == rTorrent || type == Vuze || type == BuffaloNas || type == BitComet || type == Aria2 || type == Dummy;
} }
public static boolean supportsAddByFile(Daemon type) { public static boolean supportsAddByFile(Daemon type) {
@ -351,7 +351,7 @@ public enum Daemon {
} }
public static boolean supportsForceRecheck(Daemon type) { public static boolean supportsForceRecheck(Daemon type) {
return type == uTorrent || type == BitTorrent || type == Deluge || type == rTorrent || type == Dummy; return type == uTorrent || type == BitTorrent || type == Deluge || type == rTorrent || type == Transmission || type == Dummy;
} }
public static boolean supportsExtraPassword(Daemon type) { public static boolean supportsExtraPassword(Daemon type) {

172
app/src/main/java/org/transdroid/daemon/Deluge/DelugeAdapter.java

@ -17,19 +17,12 @@
*/ */
package org.transdroid.daemon.Deluge; package org.transdroid.daemon.Deluge;
import java.io.File; import com.android.internalcopy.http.multipart.FilePart;
import java.io.FileNotFoundException; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.io.IOException; import com.android.internalcopy.http.multipart.Part;
import java.io.InputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie; import org.apache.http.cookie.Cookie;
@ -38,6 +31,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
@ -70,18 +64,21 @@ import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetLabelTask; import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTrackersTask; import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart; import java.io.File;
import com.android.internalcopy.http.multipart.MultipartEntity; import java.io.FileNotFoundException;
import com.android.internalcopy.http.multipart.Part; import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/** /**
* The daemon adapter from the Deluge torrent client. * The daemon adapter from the Deluge torrent client.
*
* @author erickok * @author erickok
*
*/ */
public class DelugeAdapter implements IDaemonAdapter { public class DelugeAdapter implements IDaemonAdapter {
@ -141,20 +138,16 @@ public class DelugeAdapter implements IDaemonAdapter {
private static final String RPC_LABEL = "label"; private static final String RPC_LABEL = "label";
private static final String RPC_TRACKERS = "trackers"; private static final String RPC_TRACKERS = "trackers";
private static final String RPC_TRACKER_STATUS = "tracker_status"; private static final String RPC_TRACKER_STATUS = "tracker_status";
private static final String[] RPC_FIELDS_ARRAY =
new String[]{RPC_NAME, RPC_STATUS, RPC_SAVEPATH, RPC_RATEDOWNLOAD, RPC_RATEUPLOAD, RPC_NUMPEERS,
RPC_NUMSEEDS, RPC_TOTALPEERS, RPC_TOTALSEEDS, RPC_ETA, RPC_DOWNLOADEDEVER, RPC_UPLOADEDEVER,
RPC_TOTALSIZE, RPC_PARTDONE, RPC_LABEL, RPC_MESSAGE, RPC_TIMEADDED, RPC_TRACKER_STATUS};
private static final String RPC_DETAILS = "files"; private static final String RPC_DETAILS = "files";
private static final String RPC_INDEX = "index"; private static final String RPC_INDEX = "index";
private static final String RPC_PATH = "path"; private static final String RPC_PATH = "path";
private static final String RPC_SIZE = "size"; private static final String RPC_SIZE = "size";
private static final String RPC_FILEPROGRESS = "file_progress"; private static final String RPC_FILEPROGRESS = "file_progress";
private static final String RPC_FILEPRIORITIES = "file_priorities"; private static final String RPC_FILEPRIORITIES = "file_priorities";
private static final String[] RPC_FIELDS_ARRAY = new String[] {
RPC_NAME, RPC_STATUS, RPC_SAVEPATH, RPC_RATEDOWNLOAD, RPC_RATEUPLOAD,
RPC_NUMPEERS, RPC_NUMSEEDS, RPC_TOTALPEERS,
RPC_TOTALSEEDS, RPC_ETA, RPC_DOWNLOADEDEVER, RPC_UPLOADEDEVER,
RPC_TOTALSIZE, RPC_PARTDONE, RPC_LABEL, RPC_MESSAGE, RPC_TIMEADDED, RPC_TRACKER_STATUS };
private DaemonSettings settings; private DaemonSettings settings;
private DefaultHttpClient httpclient; private DefaultHttpClient httpclient;
private Cookie sessionCookie; private Cookie sessionCookie;
@ -164,11 +157,11 @@ public class DelugeAdapter implements IDaemonAdapter {
this.settings = settings; this.settings = settings;
} }
public JSONArray AddTorrentByFile(String file) throws JSONException, ClientProtocolException, IOException, DaemonException { public JSONArray addTorrentByFile(String file, Log log) throws JSONException, IOException, DaemonException {
String url = buildWebUIUrl() + PATH_TO_UPLOAD; String url = buildWebUIUrl() + PATH_TO_UPLOAD;
DLog.d(LOG_NAME, "Uploading a file to the Deluge daemon: " + url); log.d(LOG_NAME, "Uploading a file to the Deluge daemon: " + url);
// Initialise the HTTP client // Initialise the HTTP client
if (httpclient == null) { if (httpclient == null) {
@ -178,7 +171,7 @@ public class DelugeAdapter implements IDaemonAdapter {
// Setup client using POST // Setup client using POST
HttpPost httppost = new HttpPost(url); HttpPost httppost = new HttpPost(url);
File upload = new File(URI.create(file)); File upload = new File(URI.create(file));
Part[] parts = { new FilePart(RPC_FILE, upload) }; Part[] parts = {new FilePart(RPC_FILE, upload)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
// Make request // Make request
@ -205,10 +198,10 @@ public class DelugeAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
ensureVersion(); ensureVersion(log);
JSONArray params = new JSONArray(); JSONArray params = new JSONArray();
@ -230,8 +223,10 @@ public class DelugeAdapter implements IDaemonAdapter {
params.put(new JSONArray()); // filter_dict params.put(new JSONArray()); // filter_dict
// params.put(-1); // cache_id // params.put(-1); // cache_id
JSONObject result = makeRequest(buildRequest(RPC_METHOD_GET, params)); JSONObject result = makeRequest(buildRequest(RPC_METHOD_GET, params), log);
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonRetrieveTorrents(result.getJSONObject(RPC_RESULT)), parseJsonRetrieveLabels(result.getJSONObject(RPC_RESULT))); return new RetrieveTaskSuccessResult((RetrieveTask) task,
parseJsonRetrieveTorrents(result.getJSONObject(RPC_RESULT)),
parseJsonRetrieveLabels(result.getJSONObject(RPC_RESULT)));
case GetTorrentDetails: case GetTorrentDetails:
@ -244,8 +239,9 @@ public class DelugeAdapter implements IDaemonAdapter {
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
params.put(dfields); // keys params.put(dfields); // keys
JSONObject dinfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params)); JSONObject dinfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params), log);
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(dinfo.getJSONObject(RPC_RESULT))); return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
parseJsonTorrentDetails(dinfo.getJSONObject(RPC_RESULT)));
case GetFileList: case GetFileList:
@ -253,36 +249,37 @@ public class DelugeAdapter implements IDaemonAdapter {
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
params.put(ffields); // keys params.put(ffields); // keys
JSONObject finfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params)); JSONObject finfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params), log);
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFileListing(finfo.getJSONObject(RPC_RESULT), task.getTargetTorrent())); return new GetFileListTaskSuccessResult((GetFileListTask) task,
parseJsonFileListing(finfo.getJSONObject(RPC_RESULT), task.getTargetTorrent()));
case AddByFile: case AddByFile:
// Request to add a torrent by local .torrent file // Request to add a torrent by local .torrent file
String file = ((AddByFileTask)task).getFile(); String file = ((AddByFileTask) task).getFile();
makeRequest(buildRequest(RPC_METHOD_ADD_FILE, AddTorrentByFile(file))); makeRequest(buildRequest(RPC_METHOD_ADD_FILE, addTorrentByFile(file, log)), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask)task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
params.put(url); params.put(url);
params.put(new JSONArray()); params.put(new JSONArray());
makeRequest(buildRequest(RPC_METHOD_ADD, params)); makeRequest(buildRequest(RPC_METHOD_ADD, params), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask)task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
// Deluge doesn't support (fully) application/x-www-form-urlencoded magnet links // Deluge doesn't support (fully) application/x-www-form-urlencoded magnet links
magnet = URLDecoder.decode(magnet, "UTF-8"); magnet = URLDecoder.decode(magnet, "UTF-8");
params.put(magnet); params.put(magnet);
params.put(new JSONArray()); params.put(new JSONArray());
makeRequest(buildRequest(RPC_METHOD_ADD_MAGNET, params)); makeRequest(buildRequest(RPC_METHOD_ADD_MAGNET, params), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
@ -291,35 +288,35 @@ public class DelugeAdapter implements IDaemonAdapter {
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
params.put(removeTask.getTargetTorrent().getUniqueID()); params.put(removeTask.getTargetTorrent().getUniqueID());
params.put(removeTask.includingData()); params.put(removeTask.includingData());
makeRequest(buildRequest(RPC_METHOD_REMOVE, params)); makeRequest(buildRequest(RPC_METHOD_REMOVE, params), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
PauseTask pauseTask = (PauseTask) task; PauseTask pauseTask = (PauseTask) task;
makeRequest(buildRequest(RPC_METHOD_PAUSE, makeRequest(buildRequest(RPC_METHOD_PAUSE, ((new JSONArray())
((new JSONArray()).put((new JSONArray()).put(pauseTask.getTargetTorrent().getUniqueID()))))); .put((new JSONArray()).put(pauseTask.getTargetTorrent().getUniqueID())))), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Resume all torrents // Resume all torrents
makeRequest(buildRequest(RPC_METHOD_PAUSE_ALL, null)); makeRequest(buildRequest(RPC_METHOD_PAUSE_ALL, null), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
ResumeTask resumeTask = (ResumeTask) task; ResumeTask resumeTask = (ResumeTask) task;
makeRequest(buildRequest(RPC_METHOD_RESUME, makeRequest(buildRequest(RPC_METHOD_RESUME, ((new JSONArray())
((new JSONArray()).put((new JSONArray()).put(resumeTask.getTargetTorrent().getUniqueID()))))); .put((new JSONArray()).put(resumeTask.getTargetTorrent().getUniqueID())))), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeRequest(buildRequest(RPC_METHOD_RESUME_ALL, null)); makeRequest(buildRequest(RPC_METHOD_RESUME_ALL, null), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -330,8 +327,9 @@ public class DelugeAdapter implements IDaemonAdapter {
// We first need a listing of all the files (because we can only set the priorities all at once) // We first need a listing of all the files (because we can only set the priorities all at once)
params.put(task.getTargetTorrent().getUniqueID()); // torrent_id params.put(task.getTargetTorrent().getUniqueID()); // torrent_id
params.put(ffields); // keys params.put(ffields); // keys
JSONObject pinfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params)); JSONObject pinfo = makeRequest(buildRequest(RPC_METHOD_STATUS, params), log);
ArrayList<TorrentFile> pfiles = parseJsonFileListing(pinfo.getJSONObject(RPC_RESULT), prioTask.getTargetTorrent()); ArrayList<TorrentFile> pfiles =
parseJsonFileListing(pinfo.getJSONObject(RPC_RESULT), prioTask.getTargetTorrent());
// Now prepare the new list of priorities // Now prepare the new list of priorities
params = new JSONArray(); params = new JSONArray();
@ -352,7 +350,7 @@ public class DelugeAdapter implements IDaemonAdapter {
params.put(pfields); // keys params.put(pfields); // keys
// Make a single call to set the priorities on all files at once // Make a single call to set the priorities on all files at once
makeRequest(buildRequest(RPC_METHOD_SETFILE, params)); makeRequest(buildRequest(RPC_METHOD_SETFILE, params), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetDownloadLocation: case SetDownloadLocation:
@ -364,7 +362,7 @@ public class DelugeAdapter implements IDaemonAdapter {
// sdlTask.getTargetTorrent().getUniqueID(), RPC_DOWNLOADLOCATION, sdlTask.getNewLocation()))); // sdlTask.getTargetTorrent().getUniqueID(), RPC_DOWNLOADLOCATION, sdlTask.getNewLocation())));
params.put(new JSONArray().put(task.getTargetTorrent().getUniqueID())); params.put(new JSONArray().put(task.getTargetTorrent().getUniqueID()));
params.put(sdlTask.getNewLocation()); params.put(sdlTask.getNewLocation());
makeRequest(buildRequest(RPC_METHOD_MOVESTORAGE, params)); makeRequest(buildRequest(RPC_METHOD_MOVESTORAGE, params), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTransferRates: case SetTransferRates:
@ -372,10 +370,10 @@ public class DelugeAdapter implements IDaemonAdapter {
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
JSONObject map = new JSONObject(); JSONObject map = new JSONObject();
map.put(RPC_MAXUPLOAD, (ratesTask.getUploadRate() == null? -1: ratesTask.getUploadRate().intValue())); map.put(RPC_MAXUPLOAD, (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate()));
map.put(RPC_MAXDOWNLOAD, (ratesTask.getDownloadRate() == null? -1: ratesTask.getDownloadRate().intValue())); map.put(RPC_MAXDOWNLOAD, (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate()));
makeRequest(buildRequest(RPC_METHOD_SETCONFIG, (new JSONArray()).put(map))); makeRequest(buildRequest(RPC_METHOD_SETCONFIG, (new JSONArray()).put(map)), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetLabel: case SetLabel:
@ -384,7 +382,7 @@ public class DelugeAdapter implements IDaemonAdapter {
SetLabelTask labelTask = (SetLabelTask) task; SetLabelTask labelTask = (SetLabelTask) task;
params.put(task.getTargetTorrent().getUniqueID()); params.put(task.getTargetTorrent().getUniqueID());
params.put(labelTask.getNewLabel() == null ? "" : labelTask.getNewLabel()); params.put(labelTask.getNewLabel() == null ? "" : labelTask.getNewLabel());
makeRequest(buildRequest(RPC_METHOD_SETLABEL, params)); makeRequest(buildRequest(RPC_METHOD_SETLABEL, params), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTrackers: case SetTrackers:
@ -401,18 +399,20 @@ public class DelugeAdapter implements IDaemonAdapter {
} }
params.put(new JSONArray().put(task.getTargetTorrent().getUniqueID())); params.put(new JSONArray().put(task.getTargetTorrent().getUniqueID()));
params.put(trackers); params.put(trackers);
makeRequest(buildRequest(RPC_METHOD_SETTRACKERS, params)); makeRequest(buildRequest(RPC_METHOD_SETTRACKERS, params), log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ForceRecheck: case ForceRecheck:
// Pause a torrent // Pause a torrent
makeRequest(buildRequest(RPC_METHOD_FORCERECHECK, makeRequest(buildRequest(RPC_METHOD_FORCERECHECK,
((new JSONArray()).put((new JSONArray()).put(task.getTargetTorrent().getUniqueID()))))); ((new JSONArray()).put((new JSONArray()).put(task.getTargetTorrent().getUniqueID())))),
log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
task.getMethod() + " is not supported by " + getType()));
} }
} catch (JSONException e) { } catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
@ -435,9 +435,10 @@ public class DelugeAdapter implements IDaemonAdapter {
return params; return params;
}*/ }*/
private void ensureVersion() throws DaemonException { private void ensureVersion(Log log) throws DaemonException {
if (version > 0) if (version > 0) {
return; return;
}
// We still need to retrieve the version number from the server // We still need to retrieve the version number from the server
// Do this by getting the web interface main html page and trying to parse the version number // Do this by getting the web interface main html page and trying to parse the version number
// Format is something like '<title>Deluge: Web UI 1.3.6</title>' // Format is something like '<title>Deluge: Web UI 1.3.6</title>'
@ -465,8 +466,9 @@ public class DelugeAdapter implements IDaemonAdapter {
for (char c : parts[2].toCharArray()) { for (char c : parts[2].toCharArray()) {
if (Character.isDigit(c)) if (Character.isDigit(c))
// Still a number; add it to the numbers string // Still a number; add it to the numbers string
{
numbers += Character.toString(c); numbers += Character.toString(c);
else { } else {
// No longer reading numbers; stop reading // No longer reading numbers; stop reading
break; break;
} }
@ -478,10 +480,10 @@ public class DelugeAdapter implements IDaemonAdapter {
} }
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
DLog.d(LOG_NAME, "Error parsing the Deluge version code as number: " + e.toString()); log.d(LOG_NAME, "Error parsing the Deluge version code as number: " + e.toString());
// Continue though, ignoring the version number // Continue though, ignoring the version number
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
// Unable to establish version number; assume an old version by setting it to version 1 // Unable to establish version number; assume an old version by setting it to version 1
@ -499,7 +501,7 @@ public class DelugeAdapter implements IDaemonAdapter {
} }
private synchronized JSONObject makeRequest(JSONObject data) throws DaemonException { private synchronized JSONObject makeRequest(JSONObject data, Log log) throws DaemonException {
try { try {
@ -543,7 +545,9 @@ public class DelugeAdapter implements IDaemonAdapter {
// Still no session cookie? // Still no session cookie?
if (sessionCookie == null) { if (sessionCookie == null) {
// Set error message and cancel the action that was requested // Set error message and cancel the action that was requested
throw new DaemonException(ExceptionType.AuthenticationFailure, "Password error? Server time difference? No (valid) cookie in response and JSON was: " + HttpHelper.convertStreamToString(instream)); throw new DaemonException(ExceptionType.AuthenticationFailure,
"Password error? Server time difference? No (valid) cookie in response and JSON was: " +
HttpHelper.convertStreamToString(instream));
} }
} }
@ -579,7 +583,9 @@ public class DelugeAdapter implements IDaemonAdapter {
JSONObject json = new JSONObject(result); JSONObject json = new JSONObject(result);
instream.close(); instream.close();
DLog.d(LOG_NAME, "Success: " + (result.length() > 300? result.substring(0, 300) + "... (" + result.length() + " chars)": result)); log.d(LOG_NAME, "Success: " +
(result.length() > 300 ? result.substring(0, 300) + "... (" + result.length() + " chars)" :
result));
// Return JSON object // Return JSON object
return json; return json;
@ -590,10 +596,10 @@ public class DelugeAdapter implements IDaemonAdapter {
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity in response object."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity in response object.");
} catch (JSONException e) { } catch (JSONException e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString()); throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -601,12 +607,12 @@ public class DelugeAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all Transmission requests. * Instantiates an HTTP client with proper credentials that can be used for all Transmission requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On missing settings * @throws DaemonException On missing settings
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {
httpclient = HttpHelper.createStandardHttpClient(settings, settings.getUsername() != null && settings.getUsername() != ""); httpclient = HttpHelper.createStandardHttpClient(settings,
settings.getUsername() != null && !settings.getUsername().equals(""));
httpclient.addRequestInterceptor(HttpHelper.gzipRequestInterceptor); httpclient.addRequestInterceptor(HttpHelper.gzipRequestInterceptor);
httpclient.addResponseInterceptor(HttpHelper.gzipResponseInterceptor); httpclient.addResponseInterceptor(HttpHelper.gzipResponseInterceptor);
@ -617,7 +623,8 @@ public class DelugeAdapter implements IDaemonAdapter {
* @return The URL of the RPC API * @return The URL of the RPC API
*/ */
private String buildWebUIUrl() { private String buildWebUIUrl() {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + (settings.getFolder() == null? "": settings.getFolder()); return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
(settings.getFolder() == null ? "" : settings.getFolder());
} }
private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONObject response) throws JSONException, DaemonException { private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONObject response) throws JSONException, DaemonException {
@ -625,7 +632,9 @@ public class DelugeAdapter implements IDaemonAdapter {
// Parse response // Parse response
ArrayList<Torrent> torrents = new ArrayList<Torrent>(); ArrayList<Torrent> torrents = new ArrayList<Torrent>();
if (response.isNull(RPC_TORRENTS)) { if (response.isNull(RPC_TORRENTS)) {
throw new DaemonException(ExceptionType.NotConnected, "Web interface probably not connected to a daemon yet, because 'torrents' is null: " + response.toString()); throw new DaemonException(ExceptionType.NotConnected,
"Web interface probably not connected to a daemon yet, because 'torrents' is null: " +
response.toString());
} }
JSONObject objects = response.getJSONObject(RPC_TORRENTS); JSONObject objects = response.getJSONObject(RPC_TORRENTS);
JSONArray names = objects.names(); JSONArray names = objects.names();
@ -637,9 +646,10 @@ public class DelugeAdapter implements IDaemonAdapter {
TorrentStatus status = convertDelugeState(tor.getString(RPC_STATUS)); TorrentStatus status = convertDelugeState(tor.getString(RPC_STATUS));
String error = tor.getString(RPC_MESSAGE); String error = tor.getString(RPC_MESSAGE);
if (tor.getString(RPC_TRACKER_STATUS).indexOf("Error") > 0) { if (tor.getString(RPC_TRACKER_STATUS).indexOf("Error") > 0) {
error += (error.length() > 0? "\n": "") + tor.getString(RPC_TRACKER_STATUS); error += (error.length() > 0 ? "\n" : "") + tor.getString(RPC_TRACKER_STATUS);
//status = TorrentStatus.Error; // Don't report this as blocking error //status = TorrentStatus.Error; // Don't report this as blocking error
} }
// @formatter:off
torrents.add(new Torrent(j, torrents.add(new Torrent(j,
names.getString(j), names.getString(j),
tor.getString(RPC_NAME), tor.getString(RPC_NAME),
@ -660,8 +670,9 @@ public class DelugeAdapter implements IDaemonAdapter {
tor.has(RPC_LABEL)? tor.getString(RPC_LABEL): null, tor.has(RPC_LABEL)? tor.getString(RPC_LABEL): null,
tor.has(RPC_TIMEADDED)? new Date((long) (tor.getDouble(RPC_TIMEADDED) * 1000L)): null, tor.has(RPC_TIMEADDED)? new Date((long) (tor.getDouble(RPC_TIMEADDED) * 1000L)): null,
null, // Not available null, // Not available
tor.getString(RPC_MESSAGE), error,
settings.getType())); settings.getType()));
// @formatter:on
} }
} }
@ -673,19 +684,22 @@ public class DelugeAdapter implements IDaemonAdapter {
private ArrayList<Label> parseJsonRetrieveLabels(JSONObject response) throws JSONException { private ArrayList<Label> parseJsonRetrieveLabels(JSONObject response) throws JSONException {
// Get the labels, of they exist (which is dependent on the plugin) // Get the labels, of they exist (which is dependent on the plugin)
if (!response.has("filters")) if (!response.has("filters")) {
return null; return null;
}
JSONObject filters = response.getJSONObject("filters"); JSONObject filters = response.getJSONObject("filters");
if (!filters.has("label")) if (!filters.has("label")) {
return null; return null;
}
JSONArray labels = filters.getJSONArray("label"); JSONArray labels = filters.getJSONArray("label");
// Parse response // Parse response
ArrayList<Label> allLabels = new ArrayList<Label>(); ArrayList<Label> allLabels = new ArrayList<Label>();
for (int i = 0; i < labels.length(); i++) { for (int i = 0; i < labels.length(); i++) {
JSONArray labelAndCount = labels.getJSONArray(i); JSONArray labelAndCount = labels.getJSONArray(i);
if (labelAndCount.getString(0).equals("All")) if (labelAndCount.getString(0).equals("All")) {
continue; // Ignore the 'All' filter, which is not an actual label continue; // Ignore the 'All' filter, which is not an actual label
}
allLabels.add(new Label(labelAndCount.getString(0), labelAndCount.getInt(1))); allLabels.add(new Label(labelAndCount.getString(0), labelAndCount.getInt(1)));
} }
return allLabels; return allLabels;
@ -704,6 +718,7 @@ public class DelugeAdapter implements IDaemonAdapter {
JSONObject file = objects.getJSONObject(j); JSONObject file = objects.getJSONObject(j);
// Add the parsed torrent to the list // Add the parsed torrent to the list
// @formatter:off
files.add(new TorrentFile( files.add(new TorrentFile(
"" + file.getInt(RPC_INDEX), "" + file.getInt(RPC_INDEX),
file.getString(RPC_PATH), file.getString(RPC_PATH),
@ -712,6 +727,7 @@ public class DelugeAdapter implements IDaemonAdapter {
file.getLong(RPC_SIZE), file.getLong(RPC_SIZE),
(long) (progress.getDouble(j) * file.getLong(RPC_SIZE)), (long) (progress.getDouble(j) * file.getLong(RPC_SIZE)),
convertDelugePriority(priorities.getInt(j)))); convertDelugePriority(priorities.getInt(j))));
// @formatter:on
} }
} }

10
app/src/main/java/org/transdroid/daemon/DummyAdapter.java

@ -25,6 +25,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask; import org.transdroid.daemon.task.AddByMagnetUrlTask;
@ -47,7 +48,6 @@ import org.transdroid.daemon.task.SetDownloadLocationTask;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetLabelTask; import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTrackersTask; import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.util.DLog;
import android.net.Uri; import android.net.Uri;
@ -124,7 +124,7 @@ public class DummyAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
@ -164,7 +164,7 @@ public class DummyAdapter implements IDaemonAdapter {
case AddByFile: case AddByFile:
String file = ((AddByFileTask) task).getFile(); String file = ((AddByFileTask) task).getFile();
DLog.d(LOG_NAME, "Adding torrent " + file); log.d(LOG_NAME, "Adding torrent " + file);
File upload = new File(URI.create(file)); File upload = new File(URI.create(file));
dummyTorrents.add(new Torrent(0, "torrent_file", upload.getName(), TorrentStatus.Queued, "/downloads/" dummyTorrents.add(new Torrent(0, "torrent_file", upload.getName(), TorrentStatus.Queued, "/downloads/"
+ file, 0, 0, 0, 0, 0, 0, -1, 0, 0, 1024 * 1024 * 1000, 0, 1F, "isos", new Date(), null, null, + file, 0, 0, 0, 0, 0, 0, -1, 0, 0, 1024 * 1024 * 1000, 0, 1F, "isos", new Date(), null, null,
@ -174,7 +174,7 @@ public class DummyAdapter implements IDaemonAdapter {
case AddByUrl: case AddByUrl:
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
DLog.d(LOG_NAME, "Adding torrent " + url); log.d(LOG_NAME, "Adding torrent " + url);
if (url == null || url.equals("")) if (url == null || url.equals(""))
throw new DaemonException(DaemonException.ExceptionType.ParsingFailed, "No url specified"); throw new DaemonException(DaemonException.ExceptionType.ParsingFailed, "No url specified");
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
@ -186,7 +186,7 @@ public class DummyAdapter implements IDaemonAdapter {
case AddByMagnetUrl: case AddByMagnetUrl:
String magnet = ((AddByMagnetUrlTask) task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
DLog.d(LOG_NAME, "Adding torrent " + magnet); log.d(LOG_NAME, "Adding torrent " + magnet);
Uri magnetUri = Uri.parse(magnet); Uri magnetUri = Uri.parse(magnet);
dummyTorrents.add(new Torrent(0, "torrent_magnet", magnetUri.getLastPathSegment(), dummyTorrents.add(new Torrent(0, "torrent_magnet", magnetUri.getLastPathSegment(),
TorrentStatus.Queued, "/downloads/" + magnetUri.getLastPathSegment(), 0, 0, 0, 0, 0, 0, -1, 0, TorrentStatus.Queued, "/downloads/" + magnetUri.getLastPathSegment(), 0, 0, 0, 0, 0, 0, -1, 0,

3
app/src/main/java/org/transdroid/daemon/IDaemonAdapter.java

@ -17,6 +17,7 @@
*/ */
package org.transdroid.daemon; package org.transdroid.daemon;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.task.DaemonTask; import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskResult;
@ -29,7 +30,7 @@ import org.transdroid.daemon.task.DaemonTaskResult;
*/ */
public interface IDaemonAdapter { public interface IDaemonAdapter {
public DaemonTaskResult executeTask(DaemonTask task); public DaemonTaskResult executeTask(Log log, DaemonTask task);
public Daemon getType(); public Daemon getType();

182
app/src/main/java/org/transdroid/daemon/Ktorrent/KtorrentAdapter.java

@ -17,15 +17,9 @@
*/ */
package org.transdroid.daemon.Ktorrent; package org.transdroid.daemon.Ktorrent;
import java.io.File; import com.android.internalcopy.http.multipart.FilePart;
import java.io.InputStream; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.io.InputStreamReader; import com.android.internalcopy.http.multipart.Part;
import java.math.BigInteger;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
@ -37,14 +31,15 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask; import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.AddByUrlTask;
@ -57,19 +52,23 @@ import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity; import java.io.File;
import com.android.internalcopy.http.multipart.Part; import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/** /**
* An adapter that allows for easy access to Ktorrent's web interface. Communication * An adapter that allows for easy access to Ktorrent's web interface. Communication is handled via HTTP GET requests
* is handled via HTTP GET requests and XML responses. * and XML responses.
*
* @author erickok * @author erickok
*
*/ */
public class KtorrentAdapter implements IDaemonAdapter { public class KtorrentAdapter implements IDaemonAdapter {
@ -86,10 +85,9 @@ public class KtorrentAdapter implements IDaemonAdapter {
private static final String RPC_URL_FILES = "/data/torrent/files.xml?torrent="; private static final String RPC_URL_FILES = "/data/torrent/files.xml?torrent=";
//private static final String RPC_COOKIE_NAME = "KT_SESSID"; //private static final String RPC_COOKIE_NAME = "KT_SESSID";
private static final String RPC_SUCCESS = "<result>OK</result>"; private static final String RPC_SUCCESS = "<result>OK</result>";
static private int retries = 0;
private DaemonSettings settings; private DaemonSettings settings;
private DefaultHttpClient httpclient; private DefaultHttpClient httpclient;
static private int retries = 0;
/** /**
* Initialises an adapter that provides operations to the Ktorrent web interface * Initialises an adapter that provides operations to the Ktorrent web interface
@ -98,71 +96,90 @@ public class KtorrentAdapter implements IDaemonAdapter {
this.settings = settings; this.settings = settings;
} }
/**
* Calculate the SHA1 hash of a password/challenge string to use with the login requests.
* @param passkey A concatenation of the challenge string and plain text password
* @return A hex-formatted SHA1-hashed string of the challenge and password strings
*/
public static String sha1Pass(String passkey) {
try {
MessageDigest m = MessageDigest.getInstance("SHA1");
byte[] data = passkey.getBytes();
m.update(data, 0, data.length);
BigInteger i = new BigInteger(1, m.digest());
return String.format("%1$040X", i).toLowerCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server // Request all torrents from server
return new RetrieveTaskSuccessResult((RetrieveTask) task, makeStatsRequest(),null); return new RetrieveTaskSuccessResult((RetrieveTask) task, makeStatsRequest(log), null);
case GetFileList: case GetFileList:
// Request file listing for a torrent // Request file listing for a torrent
return new GetFileListTaskSuccessResult((GetFileListTask) task, makeFileListRequest(task.getTargetTorrent())); return new GetFileListTaskSuccessResult((GetFileListTask) task,
makeFileListRequest(log, task.getTargetTorrent()));
case AddByFile: case AddByFile:
// Add a torrent to the server by sending the contents of a local .torrent file // Add a torrent to the server by sending the contents of a local .torrent file
String file = ((AddByFileTask)task).getFile(); String file = ((AddByFileTask) task).getFile();
makeFileUploadRequest(file); makeFileUploadRequest(log, file);
return null; return null;
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask)task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
makeActionRequest("load_torrent=" + url); makeActionRequest(log, "load_torrent=" + url);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask)task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeActionRequest("load_torrent=" + magnet); makeActionRequest(log, "load_torrent=" + magnet);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
// Note that removing with data is not supported // Note that removing with data is not supported
makeActionRequest("remove=" + task.getTargetTorrent().getUniqueID()); makeActionRequest(log, "remove=" + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeActionRequest("stop=" + task.getTargetTorrent().getUniqueID()); makeActionRequest(log, "stop=" + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Pause all torrents // Pause all torrents
makeActionRequest("stopall=true"); makeActionRequest(log, "stopall=true");
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeActionRequest("start=" + task.getTargetTorrent().getUniqueID()); makeActionRequest(log, "start=" + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeActionRequest("startall=true"); makeActionRequest(log, "startall=true");
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -184,7 +201,7 @@ public class KtorrentAdapter implements IDaemonAdapter {
} }
// It seems KTorrent's web UI does not allow for setting all priorities in one request :( // It seems KTorrent's web UI does not allow for setting all priorities in one request :(
for (TorrentFile forFile : prioTask.getForFiles()) { for (TorrentFile forFile : prioTask.getForFiles()) {
makeActionRequest(act + forFile.getKey()); makeActionRequest(log, act + forFile.getKey());
} }
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -195,7 +212,8 @@ public class KtorrentAdapter implements IDaemonAdapter {
return null; return null;
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
task.getMethod() + " is not supported by " + getType()));
} }
} catch (LoggedOutException e) { } catch (LoggedOutException e) {
@ -204,11 +222,12 @@ public class KtorrentAdapter implements IDaemonAdapter {
if (retries < 2) { if (retries < 2) {
retries++; retries++;
// Retry // Retry
DLog.d(LOG_NAME, "We were logged out without knowing: retry"); log.d(LOG_NAME, "We were logged out without knowing: retry");
return executeTask(task); return executeTask(log, task);
} else { } else {
// Never retry more than twice; in this case just return a task failure // Never retry more than twice; in this case just return a task failure
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ConnectionError, "Retried " + retries + " already, so we stopped now")); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ConnectionError,
"Retried " + retries + " already, so we stopped now"));
} }
} catch (DaemonException e) { } catch (DaemonException e) {
@ -221,13 +240,13 @@ public class KtorrentAdapter implements IDaemonAdapter {
} }
} }
private List<Torrent> makeStatsRequest() throws DaemonException, LoggedOutException { private List<Torrent> makeStatsRequest(Log log) throws DaemonException, LoggedOutException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
initialise(); initialise();
makeLoginRequest(); makeLoginRequest(log);
// Make request // Make request
HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_STATS); HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_STATS);
@ -235,29 +254,30 @@ public class KtorrentAdapter implements IDaemonAdapter {
// Read XML response // Read XML response
InputStream instream = response.getEntity().getContent(); InputStream instream = response.getEntity().getContent();
List<Torrent> torrents = StatsParser.parse(new InputStreamReader(instream), settings.getDownloadDir(), settings.getOS().getPathSeperator()); List<Torrent> torrents = StatsParser.parse(new InputStreamReader(instream), settings.getDownloadDir(),
settings.getOS().getPathSeperator());
instream.close(); instream.close();
return torrents; return torrents;
} catch (LoggedOutException e) { } catch (LoggedOutException e) {
throw e; throw e;
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, "Parsing error: " + e.toString()); log.d(LOG_NAME, "Parsing error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private List<TorrentFile> makeFileListRequest(Torrent torrent) throws DaemonException, LoggedOutException { private List<TorrentFile> makeFileListRequest(Log log, Torrent torrent) throws DaemonException, LoggedOutException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
initialise(); initialise();
makeLoginRequest(); makeLoginRequest(log);
// Make request // Make request
HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_FILES + torrent.getUniqueID()); HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_FILES + torrent.getUniqueID());
@ -270,23 +290,25 @@ public class KtorrentAdapter implements IDaemonAdapter {
// If the files list is empty, it means that this is a single-file torrent // If the files list is empty, it means that this is a single-file torrent
// We can mimic this single file form the torrent statistics itself // We can mimic this single file form the torrent statistics itself
files.add(new TorrentFile("" + 0, torrent.getName(), torrent.getName(), torrent.getLocationDir() + torrent.getName(), torrent.getTotalSize(), torrent.getDownloadedEver(), Priority.Normal)); files.add(new TorrentFile("" + 0, torrent.getName(), torrent.getName(),
torrent.getLocationDir() + torrent.getName(), torrent.getTotalSize(), torrent.getDownloadedEver(),
Priority.Normal));
return files; return files;
} catch (LoggedOutException e) { } catch (LoggedOutException e) {
throw e; throw e;
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, "Parsing error: " + e.toString()); log.d(LOG_NAME, "Parsing error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private void makeLoginRequest() throws DaemonException { private void makeLoginRequest(Log log) throws DaemonException {
try { try {
@ -294,10 +316,10 @@ public class KtorrentAdapter implements IDaemonAdapter {
HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_CHALLENGE); HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_CHALLENGE);
HttpResponse response = httpclient.execute(httpget); HttpResponse response = httpclient.execute(httpget);
InputStream instream = response.getEntity().getContent(); InputStream instream = response.getEntity().getContent();
String challengeString = HttpHelper.convertStreamToString(instream).replaceAll("\\<.*?>","").trim(); String challengeString = HttpHelper.convertStreamToString(instream).replaceAll("<.*?>", "").trim();
instream.close(); instream.close();
// Challenge string should be something like TncpX3TB8uZ0h8eqztZ6 // Challenge string should be something like TncpX3TB8uZ0h8eqztZ6
if (challengeString == null || challengeString.length() != 20) { if (challengeString.length() != 20) {
throw new DaemonException(ExceptionType.UnexpectedResponse, "No (valid) challenge string received"); throw new DaemonException(ExceptionType.UnexpectedResponse, "No (valid) challenge string received");
} }
@ -305,8 +327,10 @@ public class KtorrentAdapter implements IDaemonAdapter {
HttpPost httppost2 = new HttpPost(buildWebUIUrl() + RPC_URL_LOGIN); HttpPost httppost2 = new HttpPost(buildWebUIUrl() + RPC_URL_LOGIN);
List<NameValuePair> params = new ArrayList<NameValuePair>(3); List<NameValuePair> params = new ArrayList<NameValuePair>(3);
params.add(new BasicNameValuePair(RPC_URL_LOGIN_USER, settings.getUsername())); params.add(new BasicNameValuePair(RPC_URL_LOGIN_USER, settings.getUsername()));
params.add(new BasicNameValuePair(RPC_URL_LOGIN_PASS, "")); // Password is send (as SHA1 hex) in the challenge field params.add(new BasicNameValuePair(RPC_URL_LOGIN_PASS,
params.add(new BasicNameValuePair(RPC_URL_LOGIN_CHAL, sha1Pass(challengeString + settings.getPassword()))); // Make a SHA1 encrypted hex-formated string of the challenge code and password "")); // Password is send (as SHA1 hex) in the challenge field
params.add(new BasicNameValuePair(RPC_URL_LOGIN_CHAL, sha1Pass(challengeString +
settings.getPassword()))); // Make a SHA1 encrypted hex-formated string of the challenge code and password
httppost2.setEntity(new UrlEncodedFormEntity(params)); httppost2.setEntity(new UrlEncodedFormEntity(params));
// This sets the authentication cookie // This sets the authentication cookie
httpclient.execute(httppost2); httpclient.execute(httppost2);
@ -318,40 +342,22 @@ public class KtorrentAdapter implements IDaemonAdapter {
retries = 0; retries = 0;
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, "Login error: " + e.toString()); log.d(LOG_NAME, "Login error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error during login: " + e.toString()); log.d(LOG_NAME, "Error during login: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
/** private boolean makeActionRequest(Log log, String action) throws DaemonException, LoggedOutException {
* Calculate the SHA1 hash of a password/challenge string to use with the login requests.
* @param passkey A concatenation of the challenge string and plain text password
* @return A hex-formatted SHA1-hashed string of the challenge and password strings
*/
public static String sha1Pass(String passkey) {
try {
MessageDigest m = MessageDigest.getInstance("SHA1");
byte[] data = passkey.getBytes();
m.update(data,0,data.length);
BigInteger i = new BigInteger(1,m.digest());
return String.format("%1$040X", i).toLowerCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private boolean makeActionRequest(String action) throws DaemonException, LoggedOutException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
initialise(); initialise();
makeLoginRequest(); makeLoginRequest(log);
// Make request // Make request
HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_ACTION + action); HttpGet httpget = new HttpGet(buildWebUIUrl() + RPC_URL_ACTION + action);
@ -374,34 +380,39 @@ public class KtorrentAdapter implements IDaemonAdapter {
} catch (LoggedOutException e) { } catch (LoggedOutException e) {
throw e; throw e;
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, action + " request error: " + e.toString()); log.d(LOG_NAME, action + " request error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private boolean makeFileUploadRequest(String target) throws DaemonException, LoggedOutException { private boolean makeFileUploadRequest(Log log, String target) throws DaemonException, LoggedOutException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
initialise(); initialise();
makeLoginRequest(); makeLoginRequest(log);
// Make request // Make request
HttpPost httppost = new HttpPost(buildWebUIUrl() + RPC_URL_UPLOAD); HttpPost httppost = new HttpPost(buildWebUIUrl() + RPC_URL_UPLOAD);
File upload = new File(URI.create(target)); File upload = new File(URI.create(target));
Part[] parts = { new FilePart("load_torrent", upload) }; Part[] parts = {new FilePart("load_torrent", upload)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
// Make sure we are not automatically redirected // Make sure we are not automatically redirected
RedirectHandler handler = new RedirectHandler() { RedirectHandler handler = new RedirectHandler() {
@Override @Override
public boolean isRedirectRequested(HttpResponse response, HttpContext context) { return false; } public boolean isRedirectRequested(HttpResponse response, HttpContext context) {
return false;
}
@Override @Override
public URI getLocationURI(HttpResponse response, HttpContext context) throws ProtocolException { return null; } public URI getLocationURI(HttpResponse response, HttpContext context) throws ProtocolException {
return null;
}
}; };
httpclient.setRedirectHandler(handler); httpclient.setRedirectHandler(handler);
HttpResponse response = httpclient.execute(httppost); HttpResponse response = httpclient.execute(httppost);
@ -423,10 +434,10 @@ public class KtorrentAdapter implements IDaemonAdapter {
} catch (LoggedOutException e) { } catch (LoggedOutException e) {
throw e; throw e;
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, "File upload error: " + e.toString()); log.d(LOG_NAME, "File upload error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -448,7 +459,6 @@ public class KtorrentAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client that can be used for all Ktorrent requests. * Instantiates an HTTP client that can be used for all Ktorrent requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException Thrown on settings error * @throws DaemonException Thrown on settings error
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {

95
app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java

@ -23,6 +23,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
@ -36,6 +37,7 @@ import org.apache.http.protocol.HTTP;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
@ -62,7 +64,6 @@ import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart; import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity; import com.android.internalcopy.http.multipart.MultipartEntity;
@ -84,13 +85,13 @@ public class QbittorrentAdapter implements IDaemonAdapter {
this.settings = settings; this.settings = settings;
} }
private synchronized void ensureVersion() throws DaemonException { private synchronized void ensureVersion(Log log) throws DaemonException {
if (version > 0) if (version > 0)
return; return;
// We still need to retrieve the version number from the server // We still need to retrieve the version number from the server
// Do this by getting the web interface about page and trying to parse the version number // Do this by getting the web interface about page and trying to parse the version number
// Format is something like 'qBittorrent v2.9.7 (Web UI)' // Format is something like 'qBittorrent v2.9.7 (Web UI)'
String about = makeRequest("/about.html"); String about = makeRequest(log, "/about.html");
String aboutStartText = "qBittorrent v"; String aboutStartText = "qBittorrent v";
String aboutEndText = " (Web UI)"; String aboutEndText = " (Web UI)";
int aboutStart = about.indexOf(aboutStartText); int aboutStart = about.indexOf(aboutStartText);
@ -129,83 +130,83 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
ensureVersion(); ensureVersion(log);
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server // Request all torrents from server
JSONArray result = new JSONArray(makeRequest(version >= 30000 ? "/json/torrents" : "/json/events")); JSONArray result = new JSONArray(makeRequest(log, version >= 30000 ? "/json/torrents" : "/json/events"));
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null);
case GetTorrentDetails: case GetTorrentDetails:
// Request tracker and error details for a specific teacher // Request tracker and error details for a specific teacher
String mhash = ((GetTorrentDetailsTask) task).getTargetTorrent().getUniqueID(); String mhash = task.getTargetTorrent().getUniqueID();
JSONArray messages = new JSONArray(makeRequest("/json/propertiesTrackers/" + mhash)); JSONArray messages = new JSONArray(makeRequest(log, "/json/propertiesTrackers/" + mhash));
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
parseJsonTorrentDetails(messages)); parseJsonTorrentDetails(messages));
case GetFileList: case GetFileList:
// Request files listing for a specific torrent // Request files listing for a specific torrent
String fhash = ((GetFileListTask) task).getTargetTorrent().getUniqueID(); String fhash = task.getTargetTorrent().getUniqueID();
JSONArray files = new JSONArray(makeRequest("/json/propertiesFiles/" + fhash)); JSONArray files = new JSONArray(makeRequest(log, "/json/propertiesFiles/" + fhash));
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files)); return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files));
case AddByFile: case AddByFile:
// Upload a local .torrent file // Upload a local .torrent file
String ufile = ((AddByFileTask) task).getFile(); String ufile = ((AddByFileTask) task).getFile();
makeUploadRequest("/command/upload", ufile); makeUploadRequest("/command/upload", ufile, log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
makeRequest("/command/download", new BasicNameValuePair("urls", url)); makeRequest(log, "/command/download", new BasicNameValuePair("urls", url));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeRequest("/command/download", new BasicNameValuePair("urls", magnet)); makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeRequest((removeTask.includingData() ? "/command/deletePerm" : "/command/delete"), makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"),
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeRequest("/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Resume all torrents // Resume all torrents
makeRequest("/command/pauseall"); makeRequest(log, "/command/pauseall");
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeRequest("/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeRequest("/command/resumeall"); makeRequest(log, "/command/resumeall");
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -222,7 +223,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
// We have to make a separate request per file, it seems // We have to make a separate request per file, it seems
for (TorrentFile file : setPrio.getForFiles()) { for (TorrentFile file : setPrio.getForFiles()) {
makeRequest("/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent() makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent()
.getUniqueID()), new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair( .getUniqueID()), new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair(
"priority", newPrio)); "priority", newPrio));
} }
@ -233,14 +234,14 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// TODO: This doesn't seem to work yet // TODO: This doesn't seem to work yet
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
int dl = (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate().intValue()); int dl = (ratesTask.getDownloadRate() == null ? -1 : ratesTask.getDownloadRate());
int ul = (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate().intValue()); int ul = (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate());
// First get the preferences // First get the preferences
JSONObject prefs = new JSONObject(makeRequest("/json/preferences")); JSONObject prefs = new JSONObject(makeRequest(log, "/json/preferences"));
prefs.put("dl_limit", dl); prefs.put("dl_limit", dl);
prefs.put("up_limit", ul); prefs.put("up_limit", ul);
makeRequest("/command/setPreferences", makeRequest(log, "/command/setPreferences",
new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8))); new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -257,18 +258,16 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
} }
private String makeRequest(String path, NameValuePair... params) throws DaemonException { private String makeRequest(Log log, String path, NameValuePair... params) throws DaemonException {
try { try {
// Setup request using POST // Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path)); HttpPost httppost = new HttpPost(buildWebUIUrl(path));
List<NameValuePair> nvps = new ArrayList<NameValuePair>(); List<NameValuePair> nvps = new ArrayList<NameValuePair>();
for (NameValuePair param : params) { Collections.addAll(nvps, params);
nvps.add(param);
}
httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
return makeWebRequest(path, httppost); return makeWebRequest(httppost, log);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
@ -276,7 +275,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
private String makeUploadRequest(String path, String file) throws DaemonException { private String makeUploadRequest(String path, String file, Log log) throws DaemonException {
try { try {
@ -285,7 +284,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
File upload = new File(URI.create(file)); File upload = new File(URI.create(file));
Part[] parts = { new FilePart("torrentfile", upload) }; Part[] parts = { new FilePart("torrentfile", upload) };
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
return makeWebRequest(path, httppost); return makeWebRequest(httppost, log);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new DaemonException(ExceptionType.FileAccessError, e.toString()); throw new DaemonException(ExceptionType.FileAccessError, e.toString());
@ -293,7 +292,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
private String makeWebRequest(String path, HttpPost httppost) throws DaemonException { private String makeWebRequest(HttpPost httppost, Log log) throws DaemonException {
try { try {
@ -320,11 +319,11 @@ public class QbittorrentAdapter implements IDaemonAdapter {
return result; return result;
} }
DLog.d(LOG_NAME, "Error: No entity in HTTP response"); log.d(LOG_NAME, "Error: No entity in HTTP response");
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -332,7 +331,6 @@ public class QbittorrentAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all qBittorrent requests. * Instantiates an HTTP client with proper credentials that can be used for all qBittorrent requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {
@ -385,10 +383,31 @@ public class QbittorrentAdapter implements IDaemonAdapter {
eta = (long) (size - (size * progress)) / dlspeed; eta = (long) (size - (size * progress)) / dlspeed;
// Date added is only available in /json/propertiesGeneral on a per-torrent basis, unfortunately // Date added is only available in /json/propertiesGeneral on a per-torrent basis, unfortunately
// Add the parsed torrent to the list // Add the parsed torrent to the list
torrents.add(new Torrent((long) i, tor.getString("hash"), tor.getString("name"), parseStatus(tor // @formatter:off
.getString("state")), null, dlspeed, parseSpeed(tor.getString("upspeed")), seeders[0], seeders[1], torrents.add(new Torrent(
leechers[0], leechers[1], (int) eta, (long) (size * progress), (long) (size * ratio), size, (long) i,
(float) progress, 0f, null, null, null, null, settings.getType())); tor.getString("hash"),
tor.getString("name"),
parseStatus(tor.getString("state")),
null,
dlspeed,
parseSpeed(tor.getString("upspeed")),
seeders[0],
seeders[1],
leechers[0],
leechers[1],
(int) eta,
(long) (size * progress),
(long) (size * ratio),
size,
(float) progress,
0f,
null,
null,
null,
null,
settings.getType()));
// @formatter:on
} }
// Return the list // Return the list

170
app/src/main/java/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java

@ -17,22 +17,7 @@
*/ */
package org.transdroid.daemon.Rtorrent; package org.transdroid.daemon.Rtorrent;
import java.io.ByteArrayOutputStream; import org.transdroid.core.gui.log.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
@ -61,9 +46,24 @@ import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetLabelTask; import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import de.timroes.axmlrpc.XMLRPCClient; import de.timroes.axmlrpc.XMLRPCClient;
import de.timroes.axmlrpc.XMLRPCClient.UnauthorizdException; import de.timroes.axmlrpc.XMLRPCClient.UnauthorizdException;
import de.timroes.axmlrpc.XMLRPCException; import de.timroes.axmlrpc.XMLRPCException;
@ -88,14 +88,14 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// @formatter:off // @formatter:off
Object result = makeRtorrentCall("d.multicall", Object result = makeRtorrentCall(log,"d.multicall",
new String[] { "main", new String[] { "main",
"d.get_hash=", "d.get_hash=",
"d.get_name=", "d.get_name=",
@ -122,23 +122,24 @@ public class RtorrentAdapter implements IDaemonAdapter {
"d.get_peers_complete=", "d.get_peers_complete=",
"d.get_peers_accounted=" }); "d.get_peers_accounted=" });
// @formatter:on // @formatter:on
return new RetrieveTaskSuccessResult((RetrieveTask) task, onTorrentsRetrieved(result), lastKnownLabels); return new RetrieveTaskSuccessResult((RetrieveTask) task, onTorrentsRetrieved(result),
lastKnownLabels);
case GetTorrentDetails: case GetTorrentDetails:
// @formatter:off // @formatter:off
Object dresult = makeRtorrentCall("t.multicall", new String[] { Object dresult = makeRtorrentCall(log,"t.multicall", new String[] {
task.getTargetTorrent().getUniqueID(), task.getTargetTorrent().getUniqueID(),
"", "",
"t.get_url=" }); "t.get_url=" });
// @formatter:on // @formatter:on
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
onTorrentDetailsRetrieved(dresult)); onTorrentDetailsRetrieved(log, dresult));
case GetFileList: case GetFileList:
// @formatter:off // @formatter:off
Object fresult = makeRtorrentCall("f.multicall", new String[] { Object fresult = makeRtorrentCall(log,"f.multicall", new String[] {
task.getTargetTorrent().getUniqueID(), task.getTargetTorrent().getUniqueID(),
"", "",
"f.get_path=", "f.get_path=",
@ -149,8 +150,8 @@ public class RtorrentAdapter implements IDaemonAdapter {
"f.get_priority=", "f.get_priority=",
"f.get_frozen_path=" }); "f.get_frozen_path=" });
// @formatter:on // @formatter:on
return new GetFileListTaskSuccessResult((GetFileListTask) task, onTorrentFilesRetrieved(fresult, return new GetFileListTaskSuccessResult((GetFileListTask) task,
task.getTargetTorrent())); onTorrentFilesRetrieved(fresult, task.getTargetTorrent()));
case AddByFile: case AddByFile:
@ -159,29 +160,29 @@ public class RtorrentAdapter implements IDaemonAdapter {
FileInputStream in = new FileInputStream(file); FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[(int) file.length()]; byte[] buffer = new byte[(int) file.length()];
int read = 0; int read;
while ((read = in.read(buffer, 0, buffer.length)) > 0) { while ((read = in.read(buffer, 0, buffer.length)) > 0) {
baos.write(buffer, 0, read); baos.write(buffer, 0, read);
} }
byte[] bytes = baos.toByteArray(); byte[] bytes = baos.toByteArray();
int size = (int) file.length() * 2; int size = (int) file.length() * 2;
final int XMLRPC_EXTRA_PADDING = 1280; final int XMLRPC_EXTRA_PADDING = 1280;
makeRtorrentCall("set_xmlrpc_size_limit", new Object[] { size + XMLRPC_EXTRA_PADDING }); makeRtorrentCall(log, "set_xmlrpc_size_limit", new Object[]{size + XMLRPC_EXTRA_PADDING});
makeRtorrentCall("load_raw_start", new Object[] { bytes }); makeRtorrentCall(log, "load_raw_start", new Object[]{bytes});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
makeRtorrentCall("load_start", new String[] { url }); makeRtorrentCall(log, "load_start", new String[]{url});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeRtorrentCall("load_start", new String[] { magnet }); makeRtorrentCall(log, "load_start", new String[]{magnet});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
@ -189,57 +190,58 @@ public class RtorrentAdapter implements IDaemonAdapter {
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
if (removeTask.includingData()) { if (removeTask.includingData()) {
makeRtorrentCall("d.set_custom5", new String[] { task.getTargetTorrent().getUniqueID(), "1" }); makeRtorrentCall(log, "d.set_custom5",
new String[]{task.getTargetTorrent().getUniqueID(), "1"});
} }
makeRtorrentCall("d.erase", new String[] { task.getTargetTorrent().getUniqueID() }); makeRtorrentCall(log, "d.erase", new String[]{task.getTargetTorrent().getUniqueID()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeRtorrentCall("d.pause", new String[] { task.getTargetTorrent().getUniqueID() }); makeRtorrentCall(log, "d.pause", new String[]{task.getTargetTorrent().getUniqueID()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Resume all torrents // Resume all torrents
makeRtorrentCall("d.multicall", new String[] { "main", "d.pause=" }); makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.pause="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeRtorrentCall("d.resume", new String[] { task.getTargetTorrent().getUniqueID() }); makeRtorrentCall(log, "d.resume", new String[]{task.getTargetTorrent().getUniqueID()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeRtorrentCall("d.multicall", new String[] { "main", "d.resume=" }); makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.resume="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Stop: case Stop:
// Stop a torrent // Stop a torrent
makeRtorrentCall("d.stop", new String[] { task.getTargetTorrent().getUniqueID() }); makeRtorrentCall(log, "d.stop", new String[]{task.getTargetTorrent().getUniqueID()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case StopAll: case StopAll:
// Stop all torrents // Stop all torrents
makeRtorrentCall("d.multicall", new String[] { "main", "d.stop=" }); makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.stop="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Start: case Start:
// Start a torrent // Start a torrent
makeRtorrentCall("d.start", new String[] { task.getTargetTorrent().getUniqueID() }); makeRtorrentCall(log, "d.start", new String[]{task.getTargetTorrent().getUniqueID()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case StartAll: case StartAll:
// Start all torrents // Start all torrents
makeRtorrentCall("d.multicall", new String[] { "main", "d.start=" }); makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.start="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -249,8 +251,9 @@ public class RtorrentAdapter implements IDaemonAdapter {
String newPriority = "" + convertPriority(prioTask.getNewPriority()); String newPriority = "" + convertPriority(prioTask.getNewPriority());
// One at a time; rTorrent doesn't seem to support a multicall on a selective number of files // One at a time; rTorrent doesn't seem to support a multicall on a selective number of files
for (TorrentFile forFile : prioTask.getForFiles()) { for (TorrentFile forFile : prioTask.getForFiles()) {
makeRtorrentCall("f.set_priority", new String[] { makeRtorrentCall(log, "f.set_priority",
task.getTargetTorrent().getUniqueID() + ":f" + forFile.getKey(), newPriority }); new String[]{task.getTargetTorrent().getUniqueID() + ":f" + forFile.getKey(),
newPriority});
} }
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -258,23 +261,23 @@ public class RtorrentAdapter implements IDaemonAdapter {
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
makeRtorrentCall("set_download_rate", new String[] { (ratesTask.getDownloadRate() == null ? "0" makeRtorrentCall(log, "set_download_rate", new String[]{(ratesTask.getDownloadRate() == null ? "0" :
: ratesTask.getDownloadRate().toString() + "k") }); ratesTask.getDownloadRate().toString() + "k")});
makeRtorrentCall("set_upload_rate", new String[] { (ratesTask.getUploadRate() == null ? "0" : ratesTask makeRtorrentCall(log, "set_upload_rate", new String[]{
.getUploadRate().toString() + "k") }); (ratesTask.getUploadRate() == null ? "0" : ratesTask.getUploadRate().toString() + "k")});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetLabel: case SetLabel:
SetLabelTask labelTask = (SetLabelTask) task; SetLabelTask labelTask = (SetLabelTask) task;
makeRtorrentCall("d.set_custom1", makeRtorrentCall(log, "d.set_custom1",
new String[] { task.getTargetTorrent().getUniqueID(), labelTask.getNewLabel() }); new String[]{task.getTargetTorrent().getUniqueID(), labelTask.getNewLabel()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ForceRecheck: case ForceRecheck:
// Force re-check of data of a torrent // Force re-check of data of a torrent
makeRtorrentCall("d.check_hash", new String[] { task.getTargetTorrent().getUniqueID() }); makeRtorrentCall(log, "d.check_hash", new String[]{task.getTargetTorrent().getUniqueID()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
@ -290,8 +293,8 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
} }
private Object makeRtorrentCall(String serverMethod, Object[] arguments) throws DaemonException, private Object makeRtorrentCall(Log log, String serverMethod, Object[] arguments)
MalformedURLException { throws DaemonException, MalformedURLException {
// Initialise the HTTP client // Initialise the HTTP client
if (rpcclient == null) { if (rpcclient == null) {
@ -299,22 +302,25 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
String params = ""; String params = "";
for (Object arg : arguments) for (Object arg : arguments) {
params += " " + arg.toString(); params += " " + arg.toString();
}
try { try {
DLog.d(LOG_NAME, log.d(LOG_NAME, "Calling " + serverMethod + " with params [" +
"Calling " + serverMethod + " with params [" (params.length() > 100 ? params.substring(0, 100) + "..." : params) + " ]");
+ (params.length() > 100 ? params.substring(0, 100) + "..." : params) + " ]");
return rpcclient.call(serverMethod, arguments); return rpcclient.call(serverMethod, arguments);
} catch (XMLRPCException e) { } catch (XMLRPCException e) {
DLog.d(LOG_NAME, e.toString()); log.d(LOG_NAME, e.toString());
if (e.getCause() instanceof UnauthorizdException) if (e.getCause() instanceof UnauthorizdException) {
throw new DaemonException(ExceptionType.AuthenticationFailure, e.toString()); throw new DaemonException(ExceptionType.AuthenticationFailure, e.toString());
if (e.getCause() instanceof DaemonException) }
if (e.getCause() instanceof DaemonException) {
throw (DaemonException) e.getCause(); throw (DaemonException) e.getCause();
throw new DaemonException(ExceptionType.ConnectionError, "Error making call to " + serverMethod }
+ " with params [" + (params.length() > 100 ? params.substring(0, 100) + "..." : params) + " ]: " throw new DaemonException(ExceptionType.ConnectionError,
+ e.toString()); "Error making call to " + serverMethod + " with params [" +
(params.length() > 100 ? params.substring(0, 100) + "..." : params) + " ]: " +
e.toString());
} }
} }
@ -336,12 +342,9 @@ public class RtorrentAdapter implements IDaemonAdapter {
* @return The URL of the RPC API * @return The URL of the RPC API
*/ */
private String buildWebUIUrl() { private String buildWebUIUrl() {
return (settings.getSsl() ? "https://" : "http://") return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
+ settings.getAddress() (settings.getFolder() == null || settings.getFolder().equals("") ? DEFAULT_RPC_URL :
+ ":" settings.getFolder());
+ settings.getPort()
+ (settings.getFolder() == null || settings.getFolder().equals("") ? DEFAULT_RPC_URL : settings
.getFolder());
} }
private List<Torrent> onTorrentsRetrieved(Object response) throws DaemonException { private List<Torrent> onTorrentsRetrieved(Object response) throws DaemonException {
@ -365,7 +368,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
error = error.equals("") ? null : error; error = error.equals("") ? null : error;
// Determine the time added // Determine the time added
Date added = null; Date added;
Long addtime = null; Long addtime = null;
try { try {
addtime = Long.valueOf(((String) info[19]).trim()); addtime = Long.valueOf(((String) info[19]).trim());
@ -374,14 +377,16 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
if (addtime != null) if (addtime != null)
// Successfully received the addtime from rTorrent (which is a String like '1337089336\n') // Successfully received the addtime from rTorrent (which is a String like '1337089336\n')
{
added = new Date(addtime * 1000L); added = new Date(addtime * 1000L);
else { } else {
// rTorrent didn't have the addtime (missing plugin?): base it on creationtime instead // rTorrent didn't have the addtime (missing plugin?): base it on creationtime instead
if (info[11] instanceof Long) if (info[11] instanceof Long) {
added = new Date((Long) info[11] * 1000L); added = new Date((Long) info[11] * 1000L);
else } else {
added = new Date((Integer) info[11] * 1000L); added = new Date((Integer) info[11] * 1000L);
} }
}
// Determine the seeding time // Determine the seeding time
Date finished = null; Date finished = null;
@ -393,7 +398,9 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
if (seedingtime != null) if (seedingtime != null)
// Successfully received the seedingtime from rTorrent (which is a String like '1337089336\n') // Successfully received the seedingtime from rTorrent (which is a String like '1337089336\n')
{
finished = new Date(seedingtime * 1000L); finished = new Date(seedingtime * 1000L);
}
// Determine the label // Determine the label
String label = null; String label = null;
@ -455,11 +462,11 @@ public class RtorrentAdapter implements IDaemonAdapter {
basePath.substring(0, basePath.indexOf((String)info[17])), // locationDir basePath.substring(0, basePath.indexOf((String)info[17])), // locationDir
rateDownload, // rateDownload rateDownload, // rateDownload
(Integer)info[4], // rateUpload (Integer)info[4], // rateUpload
((Integer)info[22]).intValue(), // seedersConnected (Integer)info[22], // seedersConnected
((Integer)info[5]).intValue() + ((Integer)info[6]).intValue(), // seedersKnown (Integer)info[5] + (Integer)info[6], // seedersKnown
((Integer)info[23]).intValue(), // leechersConnected (Integer)info[23], // leechersConnected
((Integer)info[5]).intValue() + ((Integer)info[6]).intValue(), // leechersKnown (Integer)info[5] + (Integer)info[6], // leechersKnown
(rateDownload > 0? (int) ((Integer)info[12] / rateDownload): -1), // eta (bytes left / rate download, if rate > 0) (rateDownload > 0? (Integer)info[12] / rateDownload: -1), // eta (bytes left / rate download, if rate > 0)
(Integer)info[8], // downloadedEver (Integer)info[8], // downloadedEver
(Integer)info[9], // uploadedEver (Integer)info[9], // uploadedEver
(Integer)info[10], // totalSize (Integer)info[10], // totalSize
@ -476,9 +483,10 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
lastKnownLabels = new ArrayList<Label>(); lastKnownLabels = new ArrayList<Label>();
for (Entry<String, Integer> pair : labels.entrySet()) { for (Entry<String, Integer> pair : labels.entrySet()) {
if (pair.getKey() != null) if (pair.getKey() != null) {
lastKnownLabels.add(new Label(pair.getKey(), pair.getValue())); lastKnownLabels.add(new Label(pair.getKey(), pair.getValue()));
} }
}
return torrents; return torrents;
} }
@ -587,7 +595,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
} }
private TorrentDetails onTorrentDetailsRetrieved(Object response) throws DaemonException { private TorrentDetails onTorrentDetailsRetrieved(Log log, Object response) throws DaemonException {
if (response == null || !(response instanceof Object[])) { if (response == null || !(response instanceof Object[])) {
@ -601,12 +609,12 @@ public class RtorrentAdapter implements IDaemonAdapter {
List<String> trackers = new ArrayList<String>(); List<String> trackers = new ArrayList<String>();
Object[] responseList = (Object[]) response; Object[] responseList = (Object[]) response;
try { try {
for (int i = 0; i < responseList.length; i++) { for (Object aResponseList : responseList) {
Object[] info = (Object[]) responseList[i]; Object[] info = (Object[]) aResponseList;
trackers.add((String) info[0]); trackers.add((String) info[0]);
} }
} catch (Exception e) { } catch (Exception e) {
DLog.e(LOG_NAME, e.toString()); log.e(LOG_NAME, e.toString());
} }
return new TorrentDetails(trackers, null); return new TorrentDetails(trackers, null);

271
app/src/main/java/org/transdroid/daemon/Synology/SynologyAdapter.java

@ -17,13 +17,6 @@
*/ */
package org.transdroid.daemon.Synology; package org.transdroid.daemon.Synology;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
@ -31,6 +24,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
@ -55,12 +49,17 @@ import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.Collections2; import org.transdroid.daemon.util.Collections2;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/** /**
* The daemon adapter from the Synology Download Station torrent client. * The daemon adapter from the Synology Download Station torrent client.
*
*/ */
public class SynologyAdapter implements IDaemonAdapter { public class SynologyAdapter implements IDaemonAdapter {
@ -76,47 +75,48 @@ public class SynologyAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
String tid; String tid;
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
return new RetrieveTaskSuccessResult((RetrieveTask) task, tasksList(), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, tasksList(log), null);
case GetStats: case GetStats:
return null; return null;
case GetTorrentDetails: case GetTorrentDetails:
tid = task.getTargetTorrent().getUniqueID(); tid = task.getTargetTorrent().getUniqueID();
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, torrentDetails(tid)); return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
torrentDetails(log, tid));
case GetFileList: case GetFileList:
tid = task.getTargetTorrent().getUniqueID(); tid = task.getTargetTorrent().getUniqueID();
return new GetFileListTaskSuccessResult((GetFileListTask) task, fileList(tid)); return new GetFileListTaskSuccessResult((GetFileListTask) task, fileList(log, tid));
case AddByFile: case AddByFile:
return null; return null;
case AddByUrl: case AddByUrl:
String url = ((AddByUrlTask)task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
createTask(url); createTask(log, url);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
String magnet = ((AddByMagnetUrlTask)task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
createTask(magnet); createTask(log, magnet);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
tid = task.getTargetTorrent().getUniqueID(); tid = task.getTargetTorrent().getUniqueID();
removeTask(tid); removeTask(log, tid);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
tid = task.getTargetTorrent().getUniqueID(); tid = task.getTargetTorrent().getUniqueID();
pauseTask(tid); pauseTask(log, tid);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
pauseAllTasks(); pauseAllTasks(log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
tid = task.getTargetTorrent().getUniqueID(); tid = task.getTargetTorrent().getUniqueID();
resumeTask(tid); resumeTask(log, tid);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
resumeAllTasks(); resumeAllTasks(log);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetDownloadLocation: case SetDownloadLocation:
return null; return null;
@ -124,9 +124,9 @@ public class SynologyAdapter implements IDaemonAdapter {
return null; return null;
case SetTransferRates: case SetTransferRates:
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
int uploadRate = ratesTask.getUploadRate() == null ? 0 : ratesTask.getUploadRate().intValue(); int uploadRate = ratesTask.getUploadRate() == null ? 0 : ratesTask.getUploadRate();
int downloadRate = ratesTask.getDownloadRate() == null ? 0 : ratesTask.getDownloadRate().intValue(); int downloadRate = ratesTask.getDownloadRate() == null ? 0 : ratesTask.getDownloadRate();
setTransferRates(uploadRate, downloadRate); setTransferRates(log, uploadRate, downloadRate);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetAlternativeMode: case SetAlternativeMode:
default: default:
@ -149,84 +149,87 @@ public class SynologyAdapter implements IDaemonAdapter {
// Synology API // Synology API
private String login() throws DaemonException { private String login(Log log) throws DaemonException {
DLog.d(LOG_NAME, "login()"); log.d(LOG_NAME, "login()");
try { try {
return new SynoRequest( return new SynoRequest("auth.cgi", "SYNO.API.Auth", "2")
"auth.cgi", .get("&method=login&account=" + settings.getUsername() + "&passwd=" + settings.getPassword() +
"SYNO.API.Auth", "&session=DownloadStation&format=sid").getData(log).getString("sid");
"2"
).get("&method=login&account=" + settings.getUsername() + "&passwd=" + settings.getPassword() + "&session=DownloadStation&format=sid"
).getData().getString("sid");
} catch (JSONException e) { } catch (JSONException e) {
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
} }
} }
private void setTransferRates(int uploadRate, int downloadRate) throws DaemonException { private void setTransferRates(Log log, int uploadRate, int downloadRate) throws DaemonException {
authGet("SYNO.DownloadStation.Info", "1", "DownloadStation/info.cgi", authGet(log, "SYNO.DownloadStation.Info", "1", "DownloadStation/info.cgi",
"&method=setserverconfig&bt_max_upload=" + uploadRate + "&bt_max_download=" + downloadRate).ensureSuccess(); "&method=setserverconfig&bt_max_upload=" + uploadRate + "&bt_max_download=" + downloadRate)
.ensureSuccess(log);
} }
private void createTask(String uri) throws DaemonException { private void createTask(Log log, String uri) throws DaemonException {
try { try {
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=create&uri=" + URLEncoder.encode(uri, "UTF-8")).ensureSuccess(); authGet(log, "SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi",
"&method=create&uri=" + URLEncoder.encode(uri, "UTF-8")).ensureSuccess(log);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
// Never happens // Never happens
throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString()); throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString());
} }
} }
private void removeTask(String tid) throws DaemonException { private void removeTask(Log log, String tid) throws DaemonException {
List<String> tids = new ArrayList<String>(); List<String> tids = new ArrayList<String>();
tids.add(tid); tids.add(tid);
removeTasks(tids); removeTasks(log, tids);
} }
private void pauseTask(String tid) throws DaemonException { private void pauseTask(Log log, String tid) throws DaemonException {
List<String> tids = new ArrayList<String>(); List<String> tids = new ArrayList<String>();
tids.add(tid); tids.add(tid);
pauseTasks(tids); pauseTasks(log, tids);
} }
private void resumeTask(String tid) throws DaemonException { private void resumeTask(Log log, String tid) throws DaemonException {
List<String> tids = new ArrayList<String>(); List<String> tids = new ArrayList<String>();
tids.add(tid); tids.add(tid);
resumeTasks(tids); resumeTasks(log, tids);
} }
private void pauseAllTasks() throws DaemonException { private void pauseAllTasks(Log log) throws DaemonException {
List<String> tids = new ArrayList<String>(); List<String> tids = new ArrayList<String>();
for (Torrent torrent: tasksList()) { for (Torrent torrent : tasksList(log)) {
tids.add(torrent.getUniqueID()); tids.add(torrent.getUniqueID());
} }
pauseTasks(tids); pauseTasks(log, tids);
} }
private void resumeAllTasks() throws DaemonException { private void resumeAllTasks(Log log) throws DaemonException {
List<String> tids = new ArrayList<String>(); List<String> tids = new ArrayList<String>();
for (Torrent torrent: tasksList()) { for (Torrent torrent : tasksList(log)) {
tids.add(torrent.getUniqueID()); tids.add(torrent.getUniqueID());
} }
resumeTasks(tids); resumeTasks(log, tids);
} }
private void removeTasks(List<String> tids) throws DaemonException { private void removeTasks(Log log, List<String> tids) throws DaemonException {
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=delete&id=" + Collections2.joinString(tids, ",") + "").ensureSuccess(); authGet(log, "SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi",
"&method=delete&id=" + Collections2.joinString(tids, ",") + "").ensureSuccess(log);
} }
private void pauseTasks(List<String> tids) throws DaemonException { private void pauseTasks(Log log, List<String> tids) throws DaemonException {
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=pause&id=" + Collections2.joinString(tids, ",")).ensureSuccess(); authGet(log, "SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi",
"&method=pause&id=" + Collections2.joinString(tids, ",")).ensureSuccess(log);
} }
private void resumeTasks(List<String> tids) throws DaemonException { private void resumeTasks(Log log, List<String> tids) throws DaemonException {
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=resume&id=" + Collections2.joinString(tids, ",")).ensureSuccess(); authGet(log, "SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi",
"&method=resume&id=" + Collections2.joinString(tids, ",")).ensureSuccess(log);
} }
private List<Torrent> tasksList() throws DaemonException { private List<Torrent> tasksList(Log log) throws DaemonException {
try { try {
JSONArray jsonTasks = authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=list&additional=detail,transfer,tracker").getData().getJSONArray("tasks"); JSONArray jsonTasks = authGet(log, "SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi",
DLog.d(LOG_NAME, "Tasks = " + jsonTasks.toString()); "&method=list&additional=detail,transfer,tracker").getData(log).getJSONArray("tasks");
log.d(LOG_NAME, "Tasks = " + jsonTasks.toString());
List<Torrent> result = new ArrayList<Torrent>(); List<Torrent> result = new ArrayList<Torrent>();
for (int i = 0; i < jsonTasks.length(); i++) { for (int i = 0; i < jsonTasks.length(); i++) {
result.add(parseTorrent(i, jsonTasks.getJSONObject(i))); result.add(parseTorrent(i, jsonTasks.getJSONObject(i)));
@ -237,16 +240,21 @@ public class SynologyAdapter implements IDaemonAdapter {
} }
} }
private List<TorrentFile> fileList(String torrentId) throws DaemonException { private List<TorrentFile> fileList(Log log, String torrentId) throws DaemonException {
try { try {
List<TorrentFile> result = new ArrayList<TorrentFile>(); List<TorrentFile> result = new ArrayList<TorrentFile>();
JSONObject jsonTask = authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=getinfo&id=" + torrentId + "&additional=detail,transfer,tracker,file").getData().getJSONArray("tasks").getJSONObject(0); JSONObject jsonTask = authGet(log, "SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi",
DLog.d(LOG_NAME, "File list = " + jsonTask.toString()); "&method=getinfo&id=" + torrentId + "&additional=detail,transfer,tracker,file").getData(log)
.getJSONArray("tasks").getJSONObject(0);
log.d(LOG_NAME, "File list = " + jsonTask.toString());
JSONObject additional = jsonTask.getJSONObject("additional"); JSONObject additional = jsonTask.getJSONObject("additional");
if (!additional.has("file")) return result; if (!additional.has("file")) {
return result;
}
JSONArray files = additional.getJSONArray("file"); JSONArray files = additional.getJSONArray("file");
for (int i = 0; i < files.length(); i++) { for (int i = 0; i < files.length(); i++) {
JSONObject task = files.getJSONObject(i); JSONObject task = files.getJSONObject(i);
// @formatter:off
result.add(new TorrentFile( result.add(new TorrentFile(
task.getString("filename"), task.getString("filename"),
task.getString("filename"), task.getString("filename"),
@ -255,6 +263,7 @@ public class SynologyAdapter implements IDaemonAdapter {
task.getLong("size"), task.getLong("size"),
task.getLong("size_downloaded"), task.getLong("size_downloaded"),
priority(task.getString("priority")) priority(task.getString("priority"))
// @formatter:on
)); ));
} }
return result; return result;
@ -263,11 +272,13 @@ public class SynologyAdapter implements IDaemonAdapter {
} }
} }
private TorrentDetails torrentDetails(String torrentId) throws DaemonException { private TorrentDetails torrentDetails(Log log, String torrentId) throws DaemonException {
List<String> trackers = new ArrayList<String>(); List<String> trackers = new ArrayList<String>();
List<String> errors = new ArrayList<String>(); List<String> errors = new ArrayList<String>();
try { try {
JSONObject jsonTorrent = authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=getinfo&id=" + torrentId + "&additional=tracker").getData().getJSONArray("tasks").getJSONObject(0); JSONObject jsonTorrent = authGet(log, "SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi",
"&method=getinfo&id=" + torrentId + "&additional=tracker").getData(log).getJSONArray("tasks")
.getJSONObject(0);
JSONObject additional = jsonTorrent.getJSONObject("additional"); JSONObject additional = jsonTorrent.getJSONObject("additional");
if (additional.has("tracker")) { if (additional.has("tracker")) {
JSONArray tracker = additional.getJSONArray("tracker"); JSONArray tracker = additional.getJSONArray("tracker");
@ -293,7 +304,7 @@ public class SynologyAdapter implements IDaemonAdapter {
long downloaded = transfer.getLong("size_downloaded"); long downloaded = transfer.getLong("size_downloaded");
int speed = transfer.getInt("speed_download"); int speed = transfer.getInt("speed_download");
long size = jsonTorrent.getLong("size"); long size = jsonTorrent.getLong("size");
Float eta = new Float(size - downloaded) / speed; Float eta = Float.valueOf(size - downloaded) / speed;
int totalSeeders = 0; int totalSeeders = 0;
int totalLeechers = 0; int totalLeechers = 0;
if (additional.has("tracker")) { if (additional.has("tracker")) {
@ -306,6 +317,7 @@ public class SynologyAdapter implements IDaemonAdapter {
} }
} }
} }
// @formatter:off
return new Torrent( return new Torrent(
id, id,
jsonTorrent.getString("id"), jsonTorrent.getString("id"),
@ -322,79 +334,71 @@ public class SynologyAdapter implements IDaemonAdapter {
downloaded, downloaded,
transfer.getLong("size_uploaded"), transfer.getLong("size_uploaded"),
size, size,
(size == 0) ? 0 : (new Float(downloaded) / size), (size == 0) ? 0 : (Float.valueOf(downloaded) / size),
0, 0,
jsonTorrent.getString("title"), jsonTorrent.getString("title"),
new Date(detail.getLong("create_time") * 1000), new Date(detail.getLong("create_time") * 1000),
null, null,
"", "",
settings.getType() settings.getType()
// @formatter:on
); );
} }
private TorrentStatus torrentStatus(String status) { private TorrentStatus torrentStatus(String status) {
if ("downloading".equals(status)) return TorrentStatus.Downloading; if ("downloading".equals(status)) {
if ("seeding".equals(status)) return TorrentStatus.Seeding; return TorrentStatus.Downloading;
if ("finished".equals(status)) return TorrentStatus.Paused; }
if ("finishing".equals(status)) return TorrentStatus.Paused; if ("seeding".equals(status)) {
if ("waiting".equals(status)) return TorrentStatus.Waiting; return TorrentStatus.Seeding;
if ("paused".equals(status)) return TorrentStatus.Paused; }
if ("error".equals(status)) return TorrentStatus.Error; if ("finished".equals(status)) {
return TorrentStatus.Paused;
}
if ("finishing".equals(status)) {
return TorrentStatus.Paused;
}
if ("waiting".equals(status)) {
return TorrentStatus.Waiting;
}
if ("paused".equals(status)) {
return TorrentStatus.Paused;
}
if ("error".equals(status)) {
return TorrentStatus.Error;
}
return TorrentStatus.Unknown; return TorrentStatus.Unknown;
} }
private Priority priority(String priority) { private Priority priority(String priority) {
if ("low".equals(priority)) return Priority.Low; if ("low".equals(priority)) {
if ("normal".equals(priority)) return Priority.Normal; return Priority.Low;
if ("high".equals(priority)) return Priority.High; }
if ("normal".equals(priority)) {
return Priority.Normal;
}
if ("high".equals(priority)) {
return Priority.High;
}
return Priority.Off; return Priority.Off;
} }
/** /**
* Authenticated GET. If no session open, a login authGet will be done before-hand. * Authenticated GET. If no session open, a login authGet will be done before-hand.
*/ */
private SynoResponse authGet(String api, String version, String path, String params) throws DaemonException { private SynoResponse authGet(Log log, String api, String version, String path, String params)
throws DaemonException {
if (sid == null) { if (sid == null) {
sid = login(); sid = login(log);
} }
return new SynoRequest(path, api, version).get(params + "&_sid=" + sid); return new SynoRequest(path, api, version).get(params + "&_sid=" + sid);
} }
private DefaultHttpClient getHttpClient() throws DaemonException { private DefaultHttpClient getHttpClient() throws DaemonException {
if (httpClient == null) if (httpClient == null) {
httpClient = HttpHelper.createStandardHttpClient(settings, true); httpClient = HttpHelper.createStandardHttpClient(settings, true);
return httpClient;
}
private class SynoRequest {
private final String path;
private final String api;
private final String version;
public SynoRequest(String path, String api, String version) {
this.path = path;
this.api = api;
this.version = version;
} }
return httpClient;
public SynoResponse get(String params) throws DaemonException {
try {
return new SynoResponse(getHttpClient().execute(new HttpGet(buildURL(params))));
} catch (IOException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
}
private String buildURL(String params) {
return (settings.getSsl() ? "https://" : "http://")
+ settings.getAddress()
+ ":" + settings.getPort()
+ "/webapi/" + path
+ "?api=" + api
+ "&version=" + version
+ params;
}
} }
private static class SynoResponse { private static class SynoResponse {
@ -405,13 +409,13 @@ public class SynologyAdapter implements IDaemonAdapter {
this.response = response; this.response = response;
} }
public JSONObject getData() throws DaemonException { public JSONObject getData(Log log) throws DaemonException {
JSONObject json = getJson(); JSONObject json = getJson(log);
try { try {
if (json.getBoolean("success")) { if (json.getBoolean("success")) {
return json.getJSONObject("data"); return json.getJSONObject("data");
} else { } else {
DLog.e(LOG_NAME, "not a success: " + json.toString()); log.e(LOG_NAME, "not a success: " + json.toString());
throw new DaemonException(ExceptionType.AuthenticationFailure, json.getString("error")); throw new DaemonException(ExceptionType.AuthenticationFailure, json.getString("error"));
} }
} catch (JSONException e) { } catch (JSONException e) {
@ -419,11 +423,11 @@ public class SynologyAdapter implements IDaemonAdapter {
} }
} }
public JSONObject getJson() throws DaemonException { public JSONObject getJson(Log log) throws DaemonException {
try { try {
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
if (entity == null) { if (entity == null) {
DLog.e(LOG_NAME, "Error: No entity in HTTP response"); log.e(LOG_NAME, "Error: No entity in HTTP response");
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");
} }
// Read JSON response // Read JSON response
@ -436,16 +440,17 @@ public class SynologyAdapter implements IDaemonAdapter {
} catch (JSONException e) { } catch (JSONException e) {
throw new DaemonException(ExceptionType.UnexpectedResponse, "Bad JSON"); throw new DaemonException(ExceptionType.UnexpectedResponse, "Bad JSON");
} catch (IOException e) { } catch (IOException e) {
DLog.e(LOG_NAME, "getJson error: " + e.toString()); log.e(LOG_NAME, "getJson error: " + e.toString());
throw new DaemonException(ExceptionType.AuthenticationFailure, e.toString()); throw new DaemonException(ExceptionType.AuthenticationFailure, e.toString());
} }
} }
public void ensureSuccess() throws DaemonException { public void ensureSuccess(Log log) throws DaemonException {
JSONObject json = getJson(); JSONObject json = getJson(log);
try { try {
if (!json.getBoolean("success")) if (!json.getBoolean("success")) {
throw new DaemonException(ExceptionType.UnexpectedResponse, json.getString("error")); throw new DaemonException(ExceptionType.UnexpectedResponse, json.getString("error"));
}
} catch (JSONException e) { } catch (JSONException e) {
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
} }
@ -453,4 +458,30 @@ public class SynologyAdapter implements IDaemonAdapter {
} }
private class SynoRequest {
private final String path;
private final String api;
private final String version;
public SynoRequest(String path, String api, String version) {
this.path = path;
this.api = api;
this.version = version;
}
public SynoResponse get(String params) throws DaemonException {
try {
return new SynoResponse(getHttpClient().execute(new HttpGet(buildURL(params))));
} catch (IOException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
}
private String buildURL(String params) {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
"/webapi/" + path + "?api=" + api + "&version=" + version + params;
}
}
} }

181
app/src/main/java/org/transdroid/daemon/TaskQueue.java

@ -1,181 +0,0 @@
/*
* This file is part of Transdroid <http://www.transdroid.org>
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.transdroid.daemon;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.util.DLog;
public class TaskQueue implements Runnable {
private static final String LOG_NAME = "Queue";
private List<DaemonTask> queue;
private Thread worker;
private IDaemonCallback callback;
private boolean paused;;
public TaskQueue(IDaemonCallback callback) {
queue = Collections.synchronizedList(new LinkedList<DaemonTask>());
paused = true;
this.callback = callback;
worker = new Thread(this);
worker.start();
}
/**
* Enqueue a single new task to later perform.
* @param task The task to add to the queue
*/
public synchronized void enqueue(DaemonTask task) {
queue.add(task);
notifyAll();
}
/**
* Queues an old set of tasks again in the queue. This for example can be
* used to restore on old queue after an Activity was destroyed and
* is restored again.
* @param tasks A list of daemon tasks to queue
*/
public synchronized void requeue(List<DaemonTask> tasks) {
queue.addAll(tasks);
notifyAll();
}
/**
* Removes all remaining tasks from the queue. Existing operations are still
* continued and their results posted back.
*/
public synchronized void clear() {
queue.clear();
notifyAll();
}
/**
* Removes all remaining tasks from the queue that are of some specific type.
* Other remaining tasks will still be executed and running operations are
* still continued and their results posted back.
* @param class1
*/
public synchronized void clear(DaemonMethod ofType) {
Iterator<DaemonTask> task = queue.iterator();
while (task.hasNext()) {
if (task.next().getMethod() == ofType) {
task.remove();
}
}
notifyAll();
}
/**
* Returns a copy of the queue with all remaining tasks. This can be used
* to save them on an Activity destroy and restore them later using
* requeue().
* @return A list containing all remaining tasks
*/
public Queue<DaemonTask> getRemainingTasks() {
return new LinkedList<DaemonTask>(queue);
}
/**
* Request the task perfoming thread to stop all activity
*/
public synchronized void requestStop() {
paused = true;
}
/**
* Request
*/
public synchronized void start() {
paused = false;
notify();
}
@Override
public void run() {
while (true) {
if (this.paused) {
// We are going to pause
DLog.d(LOG_NAME, "Task queue pausing");
}
synchronized (this) {
while (this.paused || queue.isEmpty()) {
try {
// We are going to run again if wait() succeeded (on notify())
wait();
DLog.d(LOG_NAME, "Task queue resuming");
} catch (Exception e) {
}
}
}
processTask();
if (queue.isEmpty()) {
callback.onQueueEmpty();
// We are going to pause
DLog.d(LOG_NAME, "Task queue pausing (queue empty)");
}
}
}
private void processTask() {
// Get the task to execute
DaemonTask task = queue.remove(0);
if (task == null) {
return;
}
if (callback.isAttached())
callback.onQueuedTaskStarted(task);
// Ask the daemon adapter to perform the task (which does it synchronously)
DLog.d(LOG_NAME, "Starting task: " + task.toString());
DaemonTaskResult result = task.execute();
if (callback.isAttached())
callback.onQueuedTaskFinished(task);
// Return the result (to the UI thread)
DLog.d(LOG_NAME, "Task result: " + (result == null? "null": result.toString()));
if (result != null && !this.paused && callback.isAttached()) {
if (result.wasSuccessful()) {
callback.onTaskSuccess((DaemonTaskSuccessResult) result);
} else {
callback.onTaskFailure((DaemonTaskFailureResult) result);
}
}
}
}

39
app/src/main/java/org/transdroid/daemon/Tfb4rt/Tfb4rtAdapter.java

@ -30,6 +30,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
@ -45,7 +46,6 @@ import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.RemoveTask; import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart; import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity; import com.android.internalcopy.http.multipart.MultipartEntity;
@ -80,59 +80,59 @@ public class Tfb4rtAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server // Request all torrents from server
return new RetrieveTaskSuccessResult((RetrieveTask) task, makeStatsRequest(), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, makeStatsRequest(log), null);
case AddByFile: case AddByFile:
// Add a torrent to the server by sending the contents of a local .torrent file // Add a torrent to the server by sending the contents of a local .torrent file
String file = ((AddByFileTask) task).getFile(); String file = ((AddByFileTask) task).getFile();
makeFileUploadRequest("fileUpload", file); makeFileUploadRequest(log, "fileUpload", file);
return null; return null;
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
makeActionRequest("urlUpload", url); makeActionRequest(log, "urlUpload", url);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeActionRequest((removeTask.includingData() ? "deleteWithData" : "delete"), task.getTargetTorrent() makeActionRequest(log, (removeTask.includingData() ? "deleteWithData" : "delete"), task.getTargetTorrent()
.getUniqueID()); .getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeActionRequest("stop", task.getTargetTorrent().getUniqueID()); makeActionRequest(log, "stop", task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Pause all torrents // Pause all torrents
makeActionRequest("bulkStop", null); makeActionRequest(log, "bulkStop", null);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeActionRequest("start", task.getTargetTorrent().getUniqueID()); makeActionRequest(log, "start", task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeActionRequest("bulkStart", null); makeActionRequest(log, "bulkStart", null);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTransferRates: case SetTransferRates:
@ -150,7 +150,7 @@ public class Tfb4rtAdapter implements IDaemonAdapter {
} }
} }
private List<Torrent> makeStatsRequest() throws DaemonException { private List<Torrent> makeStatsRequest(Log log) throws DaemonException {
try { try {
@ -170,16 +170,16 @@ public class Tfb4rtAdapter implements IDaemonAdapter {
return torrents; return torrents;
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, "Parsing error: " + e.toString()); log.d(LOG_NAME, "Parsing error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private boolean makeActionRequest(String action, String target) throws DaemonException { private boolean makeActionRequest(Log log, String action, String target) throws DaemonException {
try { try {
@ -204,16 +204,16 @@ public class Tfb4rtAdapter implements IDaemonAdapter {
} }
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, action + " request error: " + e.toString()); log.d(LOG_NAME, action + " request error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private boolean makeFileUploadRequest(String action, String target) throws DaemonException { private boolean makeFileUploadRequest(Log log, String action, String target) throws DaemonException {
try { try {
@ -241,10 +241,10 @@ public class Tfb4rtAdapter implements IDaemonAdapter {
} }
} catch (DaemonException e) { } catch (DaemonException e) {
DLog.d(LOG_NAME, action + " request error: " + e.toString()); log.d(LOG_NAME, action + " request error: " + e.toString());
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -252,7 +252,6 @@ public class Tfb4rtAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client that can be used for all Torrentflux-b4rt requests. * Instantiates an HTTP client that can be used for all Torrentflux-b4rt requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {

4
app/src/main/java/org/transdroid/daemon/TorrentFilesComparator.java

@ -49,7 +49,7 @@ public class TorrentFilesComparator implements Comparator<TorrentFile> {
case PartDone: case PartDone:
return Float.compare(file1.getPartDone(), file2.getPartDone()); return Float.compare(file1.getPartDone(), file2.getPartDone());
case TotalSize: case TotalSize:
return new Long(file1.getTotalSize()).compareTo(file2.getTotalSize()); return Long.valueOf(file1.getTotalSize()).compareTo(file2.getTotalSize());
default: default:
return alphanumComparator.compare(file1.getName().toLowerCase(), file2.getName().toLowerCase()); return alphanumComparator.compare(file1.getName().toLowerCase(), file2.getName().toLowerCase());
} }
@ -58,7 +58,7 @@ public class TorrentFilesComparator implements Comparator<TorrentFile> {
case PartDone: case PartDone:
return 0 - Float.compare(file1.getPartDone(), file2.getPartDone()); return 0 - Float.compare(file1.getPartDone(), file2.getPartDone());
case TotalSize: case TotalSize:
return 0 - new Long(file1.getTotalSize()).compareTo(file2.getTotalSize()); return 0 - Long.valueOf(file1.getTotalSize()).compareTo(file2.getTotalSize());
default: default:
return 0 - alphanumComparator.compare(file1.getName().toLowerCase(), file2.getName().toLowerCase()); return 0 - alphanumComparator.compare(file1.getName().toLowerCase(), file2.getName().toLowerCase());
} }

20
app/src/main/java/org/transdroid/daemon/TorrentsComparator.java

@ -67,15 +67,15 @@ public class TorrentsComparator implements Comparator<Torrent> {
case DateDone: case DateDone:
return tor1.getDateDone().compareTo(tor2.getDateDone()); return tor1.getDateDone().compareTo(tor2.getDateDone());
case Percent: case Percent:
return new Float(tor1.getDownloadedPercentage()).compareTo(new Float(tor2.getDownloadedPercentage())); return Float.valueOf(tor1.getDownloadedPercentage()).compareTo(tor2.getDownloadedPercentage());
case DownloadSpeed: case DownloadSpeed:
return new Integer(tor1.getRateDownload()).compareTo(new Integer(tor2.getRateDownload())); return Integer.valueOf(tor1.getRateDownload()).compareTo(tor2.getRateDownload());
case UploadSpeed: case UploadSpeed:
return new Integer(tor1.getRateUpload()).compareTo(new Integer(tor2.getRateUpload())); return Integer.valueOf(tor1.getRateUpload()).compareTo(tor2.getRateUpload());
case Ratio: case Ratio:
return new Double(tor1.getRatio()).compareTo(new Double(tor2.getRatio())); return Double.valueOf(tor1.getRatio()).compareTo(tor2.getRatio());
case Size: case Size:
return new Double(tor1.getTotalSize()).compareTo(new Double(tor2.getTotalSize())); return Double.valueOf(tor1.getTotalSize()).compareTo((double) tor2.getTotalSize());
default: default:
return alphanumComparator.compare(tor1.getName().toLowerCase(), tor2.getName().toLowerCase()); return alphanumComparator.compare(tor1.getName().toLowerCase(), tor2.getName().toLowerCase());
} }
@ -92,15 +92,15 @@ public class TorrentsComparator implements Comparator<Torrent> {
case DateDone: case DateDone:
return 0 - tor1.getDateDone().compareTo(tor2.getDateDone()); return 0 - tor1.getDateDone().compareTo(tor2.getDateDone());
case Percent: case Percent:
return 0 - (new Float(tor1.getDownloadedPercentage()).compareTo(new Float(tor2.getDownloadedPercentage()))); return 0 - (Float.valueOf(tor1.getDownloadedPercentage()).compareTo(tor2.getDownloadedPercentage()));
case DownloadSpeed: case DownloadSpeed:
return 0 - (new Integer(tor1.getRateDownload()).compareTo(new Integer(tor2.getRateDownload()))); return 0 - (Integer.valueOf(tor1.getRateDownload()).compareTo(tor2.getRateDownload()));
case UploadSpeed: case UploadSpeed:
return 0 - (new Integer(tor1.getRateUpload()).compareTo(new Integer(tor2.getRateUpload()))); return 0 - (Integer.valueOf(tor1.getRateUpload()).compareTo(tor2.getRateUpload()));
case Ratio: case Ratio:
return 0 - new Double(tor1.getRatio()).compareTo(new Double(tor2.getRatio())); return 0 - Double.valueOf(tor1.getRatio()).compareTo(tor2.getRatio());
case Size: case Size:
return 0 - new Double(tor1.getTotalSize()).compareTo(new Double(tor2.getTotalSize())); return 0 - Double.valueOf(tor1.getTotalSize()).compareTo((double) tor2.getTotalSize());
default: default:
return 0 - alphanumComparator.compare(tor1.getName().toLowerCase(), tor2.getName().toLowerCase()); return 0 - alphanumComparator.compare(tor1.getName().toLowerCase(), tor2.getName().toLowerCase());
} }

179
app/src/main/java/org/transdroid/daemon/Transmission/TransmissionAdapter.java

@ -17,16 +17,6 @@
*/ */
package org.transdroid.daemon.Transmission; package org.transdroid.daemon.Transmission;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
@ -37,6 +27,7 @@ import org.base64.android.Base64.InputStream;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
@ -54,6 +45,7 @@ import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult; import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult; import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.ForceRecheckTask;
import org.transdroid.daemon.task.GetFileListTask; import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult; import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
import org.transdroid.daemon.task.GetStatsTask; import org.transdroid.daemon.task.GetStatsTask;
@ -69,14 +61,21 @@ import org.transdroid.daemon.task.SetAlternativeModeTask;
import org.transdroid.daemon.task.SetDownloadLocationTask; import org.transdroid.daemon.task.SetDownloadLocationTask;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/** /**
* The daemon adapter from the Transmission torrent client. * The daemon adapter from the Transmission torrent client.
*
* @author erickok * @author erickok
*
*/ */
public class TransmissionAdapter implements IDaemonAdapter { public class TransmissionAdapter implements IDaemonAdapter {
@ -110,11 +109,9 @@ public class TransmissionAdapter implements IDaemonAdapter {
private static final String RPC_FILE_COMPLETED = "bytesCompleted"; private static final String RPC_FILE_COMPLETED = "bytesCompleted";
private static final String RPC_FILESTAT_WANTED = "wanted"; private static final String RPC_FILESTAT_WANTED = "wanted";
private static final String RPC_FILESTAT_PRIORITY = "priority"; private static final String RPC_FILESTAT_PRIORITY = "priority";
private static String sessionToken;
private DaemonSettings settings; private DaemonSettings settings;
private DefaultHttpClient httpclient; private DefaultHttpClient httpclient;
private static String sessionToken;
private long rpcVersion = -1; private long rpcVersion = -1;
public TransmissionAdapter(DaemonSettings settings) { public TransmissionAdapter(DaemonSettings settings) {
@ -122,14 +119,14 @@ public class TransmissionAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
// Get the server version // Get the server version
if (rpcVersion <= -1) { if (rpcVersion <= -1) {
// Get server session statistics // Get server session statistics
JSONObject response = makeRequest(buildRequestObject("session-get", new JSONObject())); JSONObject response = makeRequest(log, buildRequestObject("session-get", new JSONObject()));
rpcVersion = response.getJSONObject("arguments").getInt("rpc-version"); rpcVersion = response.getJSONObject("arguments").getInt("rpc-version");
} }
@ -139,24 +136,27 @@ public class TransmissionAdapter implements IDaemonAdapter {
// Request all torrents from server // Request all torrents from server
JSONArray fields = new JSONArray(); JSONArray fields = new JSONArray();
final String[] fieldsArray = new String[] { RPC_ID, RPC_NAME, RPC_ERROR, RPC_ERRORSTRING, RPC_STATUS, final String[] fieldsArray =
RPC_DOWNLOADDIR, RPC_RATEDOWNLOAD, RPC_RATEUPLOAD, RPC_PEERSGETTING, RPC_PEERSSENDING, new String[]{RPC_ID, RPC_NAME, RPC_ERROR, RPC_ERRORSTRING, RPC_STATUS, RPC_DOWNLOADDIR,
RPC_RATEDOWNLOAD, RPC_RATEUPLOAD, RPC_PEERSGETTING, RPC_PEERSSENDING,
RPC_PEERSCONNECTED, RPC_ETA, RPC_DOWNLOADSIZE1, RPC_DOWNLOADSIZE2, RPC_UPLOADEDEVER, RPC_PEERSCONNECTED, RPC_ETA, RPC_DOWNLOADSIZE1, RPC_DOWNLOADSIZE2, RPC_UPLOADEDEVER,
RPC_TOTALSIZE, RPC_DATEADDED, RPC_DATEDONE, RPC_AVAILABLE, RPC_COMMENT }; RPC_TOTALSIZE, RPC_DATEADDED, RPC_DATEDONE, RPC_AVAILABLE, RPC_COMMENT};
for (String field : fieldsArray) { for (String field : fieldsArray) {
fields.put(field); fields.put(field);
} }
request.put("fields", fields); request.put("fields", fields);
JSONObject result = makeRequest(buildRequestObject("torrent-get", request)); JSONObject result = makeRequest(log, buildRequestObject("torrent-get", request));
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonRetrieveTorrents(result.getJSONObject("arguments")),null); return new RetrieveTaskSuccessResult((RetrieveTask) task,
parseJsonRetrieveTorrents(result.getJSONObject("arguments")), null);
case GetStats: case GetStats:
// Request the current server statistics // Request the current server statistics
JSONObject stats = makeRequest(buildRequestObject("session-get", new JSONObject())).getJSONObject("arguments"); JSONObject stats = makeRequest(log, buildRequestObject("session-get", new JSONObject()))
.getJSONObject("arguments");
return new GetStatsTaskSuccessResult((GetStatsTask) task, stats.getBoolean("alt-speed-enabled"), return new GetStatsTaskSuccessResult((GetStatsTask) task, stats.getBoolean("alt-speed-enabled"),
rpcVersion >= 12? stats.getLong("download-dir-free-space"): -1); rpcVersion >= 12 ? stats.getLong("download-dir-free-space") : -1);
case GetTorrentDetails: case GetTorrentDetails:
@ -165,10 +165,12 @@ public class TransmissionAdapter implements IDaemonAdapter {
dfields.put("trackers"); dfields.put("trackers");
dfields.put("trackerStats"); dfields.put("trackerStats");
JSONObject buildDGet = buildTorrentRequestObject(task.getTargetTorrent().getUniqueID(), null, false); JSONObject buildDGet =
buildTorrentRequestObject(task.getTargetTorrent().getUniqueID(), null, false);
buildDGet.put("fields", dfields); buildDGet.put("fields", dfields);
JSONObject getDResult = makeRequest(buildRequestObject("torrent-get", buildDGet)); JSONObject getDResult = makeRequest(log, buildRequestObject("torrent-get", buildDGet));
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(getDResult.getJSONObject("arguments"))); return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
parseJsonTorrentDetails(getDResult.getJSONObject("arguments")));
case GetFileList: case GetFileList:
@ -179,19 +181,21 @@ public class TransmissionAdapter implements IDaemonAdapter {
JSONObject buildGet = buildTorrentRequestObject(task.getTargetTorrent().getUniqueID(), null, false); JSONObject buildGet = buildTorrentRequestObject(task.getTargetTorrent().getUniqueID(), null, false);
buildGet.put("fields", ffields); buildGet.put("fields", ffields);
JSONObject getResult = makeRequest(buildRequestObject("torrent-get", buildGet)); JSONObject getResult = makeRequest(log, buildRequestObject("torrent-get", buildGet));
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFileList(getResult.getJSONObject("arguments"), task.getTargetTorrent())); return new GetFileListTaskSuccessResult((GetFileListTask) task,
parseJsonFileList(getResult.getJSONObject("arguments"), task.getTargetTorrent()));
case AddByFile: case AddByFile:
// Add a torrent to the server by sending the contents of a local .torrent file // Add a torrent to the server by sending the contents of a local .torrent file
String file = ((AddByFileTask)task).getFile(); String file = ((AddByFileTask) task).getFile();
// Encode the .torrent file's data // Encode the .torrent file's data
InputStream in = new Base64.InputStream(new FileInputStream(new File(URI.create(file))), Base64.ENCODE); InputStream in =
new Base64.InputStream(new FileInputStream(new File(URI.create(file))), Base64.ENCODE);
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
int c; int c;
while ((c = in.read())!= -1) { while ((c = in.read()) != -1) {
writer.write(c); writer.write(c);
} }
in.close(); in.close();
@ -199,58 +203,64 @@ public class TransmissionAdapter implements IDaemonAdapter {
// Request to add a torrent by Base64-encoded meta data // Request to add a torrent by Base64-encoded meta data
request.put("metainfo", writer.toString()); request.put("metainfo", writer.toString());
makeRequest(buildRequestObject("torrent-add", request)); makeRequest(log, buildRequestObject("torrent-add", request));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask)task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
request.put("filename", url); request.put("filename", url);
makeRequest(buildRequestObject("torrent-add", request)); makeRequest(log, buildRequestObject("torrent-add", request));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask)task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
request.put("filename", magnet); request.put("filename", magnet);
makeRequest(buildRequestObject("torrent-add", request)); makeRequest(log, buildRequestObject("torrent-add", request));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeRequest(buildRequestObject("torrent-remove", buildTorrentRequestObject(removeTask.getTargetTorrent().getUniqueID(), "delete-local-data", removeTask.includingData()))); makeRequest(log, buildRequestObject("torrent-remove",
buildTorrentRequestObject(removeTask.getTargetTorrent().getUniqueID(), "delete-local-data",
removeTask.includingData())));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
PauseTask pauseTask = (PauseTask) task; PauseTask pauseTask = (PauseTask) task;
makeRequest(buildRequestObject("torrent-stop", buildTorrentRequestObject(pauseTask.getTargetTorrent().getUniqueID(), null, false))); makeRequest(log, buildRequestObject("torrent-stop",
buildTorrentRequestObject(pauseTask.getTargetTorrent().getUniqueID(), null, false)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Resume all torrents // Resume all torrents
makeRequest(buildRequestObject("torrent-stop", buildTorrentRequestObject(FOR_ALL, null, false))); makeRequest(log,
buildRequestObject("torrent-stop", buildTorrentRequestObject(FOR_ALL, null, false)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
ResumeTask resumeTask = (ResumeTask) task; ResumeTask resumeTask = (ResumeTask) task;
makeRequest(buildRequestObject("torrent-start", buildTorrentRequestObject(resumeTask.getTargetTorrent().getUniqueID(), null, false))); makeRequest(log, buildRequestObject("torrent-start",
buildTorrentRequestObject(resumeTask.getTargetTorrent().getUniqueID(), null, false)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeRequest(buildRequestObject("torrent-start", buildTorrentRequestObject(FOR_ALL, null, false))); makeRequest(log,
buildRequestObject("torrent-start", buildTorrentRequestObject(FOR_ALL, null, false)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetDownloadLocation: case SetDownloadLocation:
@ -264,7 +274,7 @@ public class TransmissionAdapter implements IDaemonAdapter {
sdlrequest.put("ids", sdlids); sdlrequest.put("ids", sdlids);
sdlrequest.put("location", sdlTask.getNewLocation()); sdlrequest.put("location", sdlTask.getNewLocation());
sdlrequest.put("move", true); sdlrequest.put("move", true);
makeRequest(buildRequestObject("torrent-set-location", sdlrequest)); makeRequest(log, buildRequestObject("torrent-set-location", sdlrequest));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -279,7 +289,8 @@ public class TransmissionAdapter implements IDaemonAdapter {
prequest.put("ids", ids); prequest.put("ids", ids);
JSONArray fileids = new JSONArray(); JSONArray fileids = new JSONArray();
for (TorrentFile forfile : prioTask.getForFiles()) { for (TorrentFile forfile : prioTask.getForFiles()) {
fileids.put(Integer.parseInt(forfile.getKey())); // The keys are the indices of the files, so always numeric fileids.put(Integer.parseInt(
forfile.getKey())); // The keys are the indices of the files, so always numeric
} }
switch (prioTask.getNewPriority()) { switch (prioTask.getNewPriority()) {
case Off: case Off:
@ -299,7 +310,7 @@ public class TransmissionAdapter implements IDaemonAdapter {
break; break;
} }
makeRequest(buildRequestObject("torrent-set", prequest)); makeRequest(log, buildRequestObject("torrent-set", prequest));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTransferRates: case SetTransferRates:
@ -319,7 +330,7 @@ public class TransmissionAdapter implements IDaemonAdapter {
request.put("speed-limit-down", ratesTask.getDownloadRate().intValue()); request.put("speed-limit-down", ratesTask.getDownloadRate().intValue());
} }
makeRequest(buildRequestObject("session-set", request)); makeRequest(log, buildRequestObject("session-set", request));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetAlternativeMode: case SetAlternativeMode:
@ -327,11 +338,20 @@ public class TransmissionAdapter implements IDaemonAdapter {
// Request to set the alternative speed mode (Tutle Mode) // Request to set the alternative speed mode (Tutle Mode)
SetAlternativeModeTask altModeTask = (SetAlternativeModeTask) task; SetAlternativeModeTask altModeTask = (SetAlternativeModeTask) task;
request.put("alt-speed-enabled", altModeTask.isAlternativeModeEnabled()); request.put("alt-speed-enabled", altModeTask.isAlternativeModeEnabled());
makeRequest(buildRequestObject("session-set", request)); makeRequest(log, buildRequestObject("session-set", request));
return new DaemonTaskSuccessResult(task);
case ForceRecheck:
// Verify torrent data integrity
ForceRecheckTask verifyTask = (ForceRecheckTask) task;
makeRequest(log, buildRequestObject("torrent-verify",
buildTorrentRequestObject(verifyTask.getTargetTorrent().getUniqueID(), null, false)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
task.getMethod() + " is not supported by " + getType()));
} }
} catch (JSONException e) { } catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
@ -344,11 +364,13 @@ public class TransmissionAdapter implements IDaemonAdapter {
} }
} }
private JSONObject buildTorrentRequestObject(String torrentID, String extraKey, boolean extraValue ) throws JSONException { private JSONObject buildTorrentRequestObject(String torrentID, String extraKey, boolean extraValue)
throws JSONException {
return buildTorrentRequestObject(Long.parseLong(torrentID), extraKey, extraValue); return buildTorrentRequestObject(Long.parseLong(torrentID), extraKey, extraValue);
} }
private JSONObject buildTorrentRequestObject(long torrentID, String extraKey, boolean extraValue ) throws JSONException { private JSONObject buildTorrentRequestObject(long torrentID, String extraKey, boolean extraValue)
throws JSONException {
// Build request for one specific torrent // Build request for one specific torrent
JSONObject request = new JSONObject(); JSONObject request = new JSONObject();
@ -374,7 +396,7 @@ public class TransmissionAdapter implements IDaemonAdapter {
return request; return request;
} }
private synchronized JSONObject makeRequest(JSONObject data) throws DaemonException { private synchronized JSONObject makeRequest(Log log, JSONObject data) throws DaemonException {
try { try {
@ -395,7 +417,7 @@ public class TransmissionAdapter implements IDaemonAdapter {
} }
// Execute // Execute
DLog.d(LOG_NAME, "Execute " + data.getString("method") + " request to " + httppost.getURI().toString()); log.d(LOG_NAME, "Execute " + data.getString("method") + " request to " + httppost.getURI().toString());
HttpResponse response = httpclient.execute(httppost); HttpResponse response = httpclient.execute(httppost);
// Authentication error? // Authentication error?
@ -408,11 +430,12 @@ public class TransmissionAdapter implements IDaemonAdapter {
if (response.getStatusLine().getStatusCode() == 409) { if (response.getStatusLine().getStatusCode() == 409) {
// Retry post, but this time with the new session token that was encapsulated in the 409 response // Retry post, but this time with the new session token that was encapsulated in the 409 response
DLog.d(LOG_NAME, "Receive HTTP 409 with new session code; now try again for the actual request"); log.d(LOG_NAME, "Receive HTTP 409 with new session code; now try again for the actual request");
sessionToken = response.getFirstHeader(sessionHeader).getValue(); sessionToken = response.getFirstHeader(sessionHeader).getValue();
httppost.addHeader(sessionHeader, sessionToken); httppost.addHeader(sessionHeader, sessionToken);
DLog.d(LOG_NAME, "Retry to execute " + data.getString("method") + " request, now with " + sessionHeader log.d(LOG_NAME,
+ ": " + sessionToken); "Retry to execute " + data.getString("method") + " request, now with " + sessionHeader + ": " +
sessionToken);
response = httpclient.execute(httppost); response = httpclient.execute(httppost);
} }
@ -423,9 +446,8 @@ public class TransmissionAdapter implements IDaemonAdapter {
// Read JSON response // Read JSON response
java.io.InputStream instream = entity.getContent(); java.io.InputStream instream = entity.getContent();
String result = HttpHelper.convertStreamToString(instream); String result = HttpHelper.convertStreamToString(instream);
DLog.d(LOG_NAME, log.d(LOG_NAME, "Received content response starting with " +
"Received content response starting with " (result.length() > 100 ? result.substring(0, 100) + "..." : result));
+ (result.length() > 100 ? result.substring(0, 100) + "..." : result));
JSONObject json = new JSONObject(result); JSONObject json = new JSONObject(result);
instream.close(); instream.close();
@ -433,16 +455,16 @@ public class TransmissionAdapter implements IDaemonAdapter {
return json; return json;
} }
DLog.d(LOG_NAME, "Error: No entity in HTTP response"); log.d(LOG_NAME, "Error: No entity in HTTP response");
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");
} catch (DaemonException e) { } catch (DaemonException e) {
throw e; throw e;
} catch (JSONException e) { } catch (JSONException e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
@ -450,7 +472,6 @@ public class TransmissionAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all Transmission requests. * Instantiates an HTTP client with proper credentials that can be used for all Transmission requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {
@ -467,11 +488,12 @@ public class TransmissionAdapter implements IDaemonAdapter {
// Allow the user's folder setting to override /transmission (as per Transmission's rpc-url option) // Allow the user's folder setting to override /transmission (as per Transmission's rpc-url option)
folder = settings.getFolder().trim(); folder = settings.getFolder().trim();
// Strip any trailing slashes // Strip any trailing slashes
if (folder.endsWith("/")) if (folder.endsWith("/")) {
folder = folder.substring(0, folder.length() - 1); folder = folder.substring(0, folder.length() - 1);
} }
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + folder }
+ "/rpc"; return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
folder + "/rpc";
} }
private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONObject response) throws JSONException { private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONObject response) throws JSONException {
@ -482,7 +504,7 @@ public class TransmissionAdapter implements IDaemonAdapter {
for (int i = 0; i < rarray.length(); i++) { for (int i = 0; i < rarray.length(); i++) {
JSONObject tor = rarray.getJSONObject(i); JSONObject tor = rarray.getJSONObject(i);
// Add the parsed torrent to the list // Add the parsed torrent to the list
float have = (float)(tor.getLong(RPC_DOWNLOADSIZE1) + tor.getLong(RPC_DOWNLOADSIZE2)); float have = (float) (tor.getLong(RPC_DOWNLOADSIZE1) + tor.getLong(RPC_DOWNLOADSIZE2));
long total = tor.getLong(RPC_TOTALSIZE); long total = tor.getLong(RPC_TOTALSIZE);
// Error is a number, see https://trac.transmissionbt.com/browser/trunk/libtransmission/transmission.h#L1747 // Error is a number, see https://trac.transmissionbt.com/browser/trunk/libtransmission/transmission.h#L1747
// We only consider it a real error if it is local (blocking), which is error code 3 // We only consider it a real error if it is local (blocking), which is error code 3
@ -490,16 +512,18 @@ public class TransmissionAdapter implements IDaemonAdapter {
String errorString = tor.getString(RPC_ERRORSTRING).trim(); String errorString = tor.getString(RPC_ERRORSTRING).trim();
String commentString = tor.getString(RPC_COMMENT).trim(); String commentString = tor.getString(RPC_COMMENT).trim();
if (!commentString.equals("")) { if (!commentString.equals("")) {
errorString = errorString.equals("")? commentString : errorString + "\n" + commentString; errorString = errorString.equals("") ? commentString : errorString + "\n" + commentString;
} }
String locationDir = tor.getString(RPC_DOWNLOADDIR); String locationDir = tor.getString(RPC_DOWNLOADDIR);
if (!locationDir.endsWith(settings.getOS().getPathSeperator())) if (!locationDir.endsWith(settings.getOS().getPathSeperator())) {
locationDir += settings.getOS().getPathSeperator(); locationDir += settings.getOS().getPathSeperator();
}
// @formatter:off
torrents.add(new Torrent( torrents.add(new Torrent(
tor.getInt(RPC_ID), tor.getInt(RPC_ID),
null, null,
tor.getString(RPC_NAME), tor.getString(RPC_NAME),
hasError? TorrentStatus.Error: getStatus(tor.getInt(RPC_STATUS)), hasError ? TorrentStatus.Error : getStatus(tor.getInt(RPC_STATUS)),
locationDir, locationDir,
tor.getInt(RPC_RATEDOWNLOAD), tor.getInt(RPC_RATEDOWNLOAD),
tor.getInt(RPC_RATEUPLOAD), tor.getInt(RPC_RATEUPLOAD),
@ -512,13 +536,14 @@ public class TransmissionAdapter implements IDaemonAdapter {
tor.getLong(RPC_UPLOADEDEVER), tor.getLong(RPC_UPLOADEDEVER),
tor.getLong(RPC_TOTALSIZE), tor.getLong(RPC_TOTALSIZE),
//(float) tor.getDouble(RPC_PERCENTDONE), //(float) tor.getDouble(RPC_PERCENTDONE),
(total == 0? 0: have/(float)total), (total == 0 ? 0 : have / (float) total),
(total == 0? 0: (have+(float)tor.getLong(RPC_AVAILABLE))/(float)total), (total == 0 ? 0 : (have + (float) tor.getLong(RPC_AVAILABLE)) / (float) total),
null, // No label/category/group support in the RPC API for now // No label/category/group support in the RPC API for now
null,
new Date(tor.getLong(RPC_DATEADDED) * 1000L), new Date(tor.getLong(RPC_DATEADDED) * 1000L),
new Date(tor.getLong(RPC_DATEDONE) * 1000L), new Date(tor.getLong(RPC_DATEDONE) * 1000L),
errorString, errorString, settings.getType()));
settings.getType())); // @formatter:on
} }
// Return the list // Return the list
@ -563,14 +588,16 @@ public class TransmissionAdapter implements IDaemonAdapter {
for (int i = 0; i < files.length(); i++) { for (int i = 0; i < files.length(); i++) {
JSONObject file = files.getJSONObject(i); JSONObject file = files.getJSONObject(i);
JSONObject stat = fileStats.getJSONObject(i); JSONObject stat = fileStats.getJSONObject(i);
// @formatter:off
torrentfiles.add(new TorrentFile( torrentfiles.add(new TorrentFile(
"" + i, String.valueOf(i),
file.getString(RPC_FILE_NAME), file.getString(RPC_FILE_NAME),
file.getString(RPC_FILE_NAME), file.getString(RPC_FILE_NAME),
torrent.getLocationDir() + file.getString(RPC_FILE_NAME), torrent.getLocationDir() + file.getString(RPC_FILE_NAME),
file.getLong(RPC_FILE_LENGTH), file.getLong(RPC_FILE_LENGTH),
file.getLong(RPC_FILE_COMPLETED), file.getLong(RPC_FILE_COMPLETED),
convertTransmissionPriority(stat.getBoolean(RPC_FILESTAT_WANTED), stat.getInt(RPC_FILESTAT_PRIORITY)))); convertTransmissionPriority(stat.getBoolean(RPC_FILESTAT_WANTED), stat.getInt(RPC_FILESTAT_PRIORITY))));
// @formatter:on
} }
} }

275
app/src/main/java/org/transdroid/daemon/Utorrent/UtorrentAdapter.java

@ -17,25 +17,18 @@
*/ */
package org.transdroid.daemon.Utorrent; package org.transdroid.daemon.Utorrent;
import java.io.File; import com.android.internalcopy.http.multipart.FilePart;
import java.io.FileNotFoundException; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.io.IOException; import com.android.internalcopy.http.multipart.Part;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
@ -66,28 +59,59 @@ import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTrackersTask; import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.task.StartTask; import org.transdroid.daemon.task.StartTask;
import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart; import java.io.File;
import com.android.internalcopy.http.multipart.MultipartEntity; import java.io.FileNotFoundException;
import com.android.internalcopy.http.multipart.Part; import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/** /**
* An adapter that allows for easy access to uTorrent torrent data. Communication * An adapter that allows for easy access to uTorrent torrent data. Communication is handled via authenticated JSON-RPC
* is handled via authenticated JSON-RPC HTTP GET requests and responses. * HTTP GET requests and responses.
*
* @author erickok * @author erickok
*
*/ */
public class UtorrentAdapter implements IDaemonAdapter { public class UtorrentAdapter implements IDaemonAdapter {
private static final String LOG_NAME = "uTorrent daemon"; private static final String LOG_NAME = "uTorrent daemon";
private static final String RPC_URL_HASH = "&hash="; private static final String RPC_URL_HASH = "&hash=";
private static final int NAME_IDX = 0;
private static final int COUNT_IDX = 1;
// These are the positions inside the JSON response array of a torrent
// See http://forum.utorrent.com/viewtopic.php?id=25661
private static final int RPC_HASH_IDX = 0;
private static final int RPC_STATUS_IDX = 1;
private static final int RPC_NAME_IDX = 2;
private static final int RPC_SIZE_IDX = 3;
private static final int RPC_PARTDONE = 4;
private static final int RPC_DOWNLOADED_IDX = 5;
private static final int RPC_UPLOADED_IDX = 6;
private static final int RPC_DOWNLOADSPEED_IDX = 9;
private static final int RPC_UPLOADSPEED_IDX = 8;
private static final int RPC_ETA_IDX = 10;
private static final int RPC_LABEL_IDX = 11;
private static final int RPC_PEERSCONNECTED_IDX = 12;
private static final int RPC_PEERSINSWARM_IDX = 13;
private static final int RPC_SEEDSCONNECTED_IDX = 14;
private static final int RPC_SEEDSINSWARM_IDX = 15;
private static final int RPC_AVAILABILITY_IDX = 16;
private static final int RPC_ADDEDON_IDX = 23;
private static final int RPC_COMPLETEDON_IDX = 24;
// These are the positions inside the JSON response array of a torrent
// See http://forum.utorrent.com/viewtopic.php?id=25661
private static final int RPC_FILENAME_IDX = 0;
private static final int RPC_FILESIZE_IDX = 1;
private static final int RPC_FILEDOWNLOADED_IDX = 2;
private static final int RPC_FILEPRIORITY_IDX = 3;
private static String authtoken;
private DaemonSettings settings; private DaemonSettings settings;
private DefaultHttpClient httpclient; private DefaultHttpClient httpclient;
private static String authtoken;
/** /**
* Initialises an adapter that provides operations to the uTorrent web daemon * Initialises an adapter that provides operations to the uTorrent web daemon
@ -97,49 +121,56 @@ public class UtorrentAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// Request all torrents from server // Request all torrents from server
JSONObject result = makeUtorrentRequest("&list=1"); JSONObject result = makeUtorrentRequest(log, "&list=1");
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonRetrieveTorrents(result.getJSONArray("torrents")),parseJsonRetrieveGetLabels(result.getJSONArray("label"))); return new RetrieveTaskSuccessResult((RetrieveTask) task,
parseJsonRetrieveTorrents(result.getJSONArray("torrents")),
parseJsonRetrieveGetLabels(result.getJSONArray("label")));
case GetTorrentDetails: case GetTorrentDetails:
// Request fine details of a specific torrent // Request fine details of a specific torrent
JSONObject dresult = makeUtorrentRequest("&action=getprops" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); JSONObject dresult = makeUtorrentRequest(log,
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(dresult.getJSONArray("props"))); "&action=getprops" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
parseJsonTorrentDetails(dresult.getJSONArray("props")));
case GetFileList: case GetFileList:
// Get the file listing of a torrent // Get the file listing of a torrent
JSONObject files = makeUtorrentRequest("&action=getfiles" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); JSONObject files = makeUtorrentRequest(log,
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFileListing(files.getJSONArray("files").getJSONArray(1), task.getTargetTorrent())); "&action=getfiles" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new GetFileListTaskSuccessResult((GetFileListTask) task,
parseJsonFileListing(files.getJSONArray("files").getJSONArray(1), task.getTargetTorrent()));
case AddByFile: case AddByFile:
// Add a torrent to the server by sending the contents of a local .torrent file // Add a torrent to the server by sending the contents of a local .torrent file
String file = ((AddByFileTask)task).getFile(); String file = ((AddByFileTask) task).getFile();
uploadTorrentFile(file); uploadTorrentFile(file);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask)task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
if (url == null || url.equals("")) if (url == null || url.equals("")) {
throw new DaemonException(DaemonException.ExceptionType.ParsingFailed, "No url specified"); throw new DaemonException(DaemonException.ExceptionType.ParsingFailed, "No url specified");
makeUtorrentRequest("&action=add-url&s=" + URLEncoder.encode(url, "UTF-8")); }
makeUtorrentRequest(log, "&action=add-url&s=" + URLEncoder.encode(url, "UTF-8"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask)task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeUtorrentRequest("&action=add-url&s=" + URLEncoder.encode(magnet, "UTF-8")); makeUtorrentRequest(log, "&action=add-url&s=" + URLEncoder.encode(magnet, "UTF-8"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
@ -147,46 +178,48 @@ public class UtorrentAdapter implements IDaemonAdapter {
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
if (removeTask.includingData()) { if (removeTask.includingData()) {
makeUtorrentRequest("&action=removedata" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log,
"&action=removedata" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
} else { } else {
makeUtorrentRequest("&action=remove" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log,
"&action=remove" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
} }
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Pause: case Pause:
// Pause a torrent // Pause a torrent
makeUtorrentRequest("&action=pause" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log, "&action=pause" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case PauseAll: case PauseAll:
// Pause all torrents // Pause all torrents
makeUtorrentRequest("&action=pause" + getAllHashes()); makeUtorrentRequest(log, "&action=pause" + getAllHashes(log));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
// Resume a torrent // Resume a torrent
makeUtorrentRequest("&action=unpause" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log, "&action=unpause" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeUtorrentRequest("&action=unpause" + getAllHashes()); makeUtorrentRequest(log, "&action=unpause" + getAllHashes(log));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Stop: case Stop:
// Stop a torrent // Stop a torrent
makeUtorrentRequest("&action=stop" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log, "&action=stop" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case StopAll: case StopAll:
// Stop all torrents // Stop all torrents
makeUtorrentRequest("&action=stop" + getAllHashes()); makeUtorrentRequest(log, "&action=stop" + getAllHashes(log));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Start: case Start:
@ -194,16 +227,18 @@ public class UtorrentAdapter implements IDaemonAdapter {
// Start a torrent (maybe forced) // Start a torrent (maybe forced)
StartTask startTask = (StartTask) task; StartTask startTask = (StartTask) task;
if (startTask.isForced()) { if (startTask.isForced()) {
makeUtorrentRequest("&action=forcestart" + RPC_URL_HASH + startTask.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log,
"&action=forcestart" + RPC_URL_HASH + startTask.getTargetTorrent().getUniqueID());
} else { } else {
makeUtorrentRequest("&action=start" + RPC_URL_HASH + startTask.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log,
"&action=start" + RPC_URL_HASH + startTask.getTargetTorrent().getUniqueID());
} }
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case StartAll: case StartAll:
// Start all torrents // Start all torrents
makeUtorrentRequest("&action=start" + getAllHashes()); makeUtorrentRequest(log, "&action=start" + getAllHashes(log));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -214,25 +249,26 @@ public class UtorrentAdapter implements IDaemonAdapter {
for (TorrentFile forFile : prioTask.getForFiles()) { for (TorrentFile forFile : prioTask.getForFiles()) {
prioUrl += "&f=" + forFile.getKey(); prioUrl += "&f=" + forFile.getKey();
} }
makeUtorrentRequest("&action=setprio" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID() + prioUrl); makeUtorrentRequest(log,
"&action=setprio" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID() + prioUrl);
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTransferRates: case SetTransferRates:
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
makeUtorrentRequest( makeUtorrentRequest(log, "&action=setsetting&s=ul_auto_throttle&v=0&s=max_ul_rate&v=" +
"&action=setsetting&s=ul_auto_throttle&v=0&s=max_ul_rate&v=" + (ratesTask.getUploadRate() == null ? 0 : ratesTask.getUploadRate()) +
(ratesTask.getUploadRate() == null? 0: ratesTask.getUploadRate().intValue()) +
"&s=max_dl_rate&v=" + "&s=max_dl_rate&v=" +
(ratesTask.getDownloadRate() == null? 0: ratesTask.getDownloadRate().intValue())); (ratesTask.getDownloadRate() == null ? 0 : ratesTask.getDownloadRate()));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetLabel: case SetLabel:
// Set the label of some torrent // Set the label of some torrent
SetLabelTask labelTask = (SetLabelTask) task; SetLabelTask labelTask = (SetLabelTask) task;
makeUtorrentRequest("&action=setprops" + RPC_URL_HASH + labelTask.getTargetTorrent().getUniqueID() + makeUtorrentRequest(log,
"&action=setprops" + RPC_URL_HASH + labelTask.getTargetTorrent().getUniqueID() +
"&s=label&v=" + URLEncoder.encode(labelTask.getNewLabel(), "UTF-8")); "&s=label&v=" + URLEncoder.encode(labelTask.getNewLabel(), "UTF-8"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -243,20 +279,22 @@ public class UtorrentAdapter implements IDaemonAdapter {
// Build list of tracker lines, separated by a \r\n // Build list of tracker lines, separated by a \r\n
String newTrackersText = ""; String newTrackersText = "";
for (String tracker : trackersTask.getNewTrackers()) { for (String tracker : trackersTask.getNewTrackers()) {
newTrackersText += (newTrackersText.length() == 0? "": "\r\n") + tracker; newTrackersText += (newTrackersText.length() == 0 ? "" : "\r\n") + tracker;
} }
makeUtorrentRequest("&action=setprops" + RPC_URL_HASH + trackersTask.getTargetTorrent().getUniqueID() + makeUtorrentRequest(log,
"&action=setprops" + RPC_URL_HASH + trackersTask.getTargetTorrent().getUniqueID() +
"&s=trackers&v=" + URLEncoder.encode(newTrackersText, "UTF-8")); "&s=trackers&v=" + URLEncoder.encode(newTrackersText, "UTF-8"));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case ForceRecheck: case ForceRecheck:
// Force re-check of data on a torrent // Force re-check of data on a torrent
makeUtorrentRequest("&action=recheck" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID()); makeUtorrentRequest(log, "&action=recheck" + RPC_URL_HASH + task.getTargetTorrent().getUniqueID());
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
task.getMethod() + " is not supported by " + getType()));
} }
} catch (JSONException e) { } catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
@ -265,15 +303,13 @@ public class UtorrentAdapter implements IDaemonAdapter {
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString()));
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, e.toString())); return new DaemonTaskFailureResult(task,
new DaemonException(ExceptionType.MethodUnsupported, e.toString()));
} catch (IOException e) { } catch (IOException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ConnectionError, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ConnectionError, e.toString()));
} }
} }
private static final int NAME_IDX = 0;
private static final int COUNT_IDX = 1;
private ArrayList<Label> parseJsonRetrieveGetLabels(JSONArray lresults) throws JSONException { private ArrayList<Label> parseJsonRetrieveGetLabels(JSONArray lresults) throws JSONException {
// Parse response // Parse response
@ -282,20 +318,17 @@ public class UtorrentAdapter implements IDaemonAdapter {
JSONArray lab = lresults.getJSONArray(i); JSONArray lab = lresults.getJSONArray(i);
String name = lab.getString(NAME_IDX); String name = lab.getString(NAME_IDX);
int count = lab.getInt(COUNT_IDX); int count = lab.getInt(COUNT_IDX);
labels.add(new Label( labels.add(new Label(name, count));
name,
count
));
} }
return labels; return labels;
} }
private JSONObject makeUtorrentRequest(String addToUrl) throws DaemonException { private JSONObject makeUtorrentRequest(Log log, String addToUrl) throws DaemonException {
return makeUtorrentRequest(addToUrl, 0); return makeUtorrentRequest(log, addToUrl, 0);
} }
private JSONObject makeUtorrentRequest(String addToUrl, int retried) throws DaemonException { private JSONObject makeUtorrentRequest(Log log, String addToUrl, int retried) throws DaemonException {
try { try {
@ -317,9 +350,11 @@ public class UtorrentAdapter implements IDaemonAdapter {
// Auth token was invalidated; retry at max 3 times // Auth token was invalidated; retry at max 3 times
authtoken = null; // So that ensureToken() will request a new token on the next try authtoken = null; // So that ensureToken() will request a new token on the next try
if (retried < 2) { if (retried < 2) {
return makeUtorrentRequest(addToUrl, retried++); return makeUtorrentRequest(log, addToUrl, ++retried);
} }
throw new DaemonException(ExceptionType.AuthenticationFailure, "Response was '" + result.replace("\n", "") + "' instead of a proper JSON object (and we used auth token '" + authtoken + "')"); throw new DaemonException(ExceptionType.AuthenticationFailure,
"Response was '" + result.replace("\n", "") +
"' instead of a proper JSON object (and we used auth token '" + authtoken + "')");
} }
JSONObject json = new JSONObject(result); JSONObject json = new JSONObject(result);
instream.close(); instream.close();
@ -328,16 +363,16 @@ public class UtorrentAdapter implements IDaemonAdapter {
} catch (DaemonException e) { } catch (DaemonException e) {
throw e; throw e;
} catch (JSONException e) { } catch (JSONException e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
} catch (Exception e) { } catch (Exception e) {
DLog.d(LOG_NAME, "Error: " + e.toString()); log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private synchronized void ensureToken() throws IOException, ClientProtocolException, DaemonException { private synchronized void ensureToken() throws IOException, DaemonException {
// Make sure we have a valid token // Make sure we have a valid token
if (authtoken == null) { if (authtoken == null) {
@ -349,20 +384,22 @@ public class UtorrentAdapter implements IDaemonAdapter {
// Parse the response HTML // Parse the response HTML
HttpResponse response = httpclient.execute(httpget); HttpResponse response = httpclient.execute(httpget);
if (response.getStatusLine().getStatusCode() == 401) { if (response.getStatusLine().getStatusCode() == 401) {
throw new DaemonException(ExceptionType.AuthenticationFailure, "Auth denied (401) on token.html retrieval"); throw new DaemonException(ExceptionType.AuthenticationFailure,
"Auth denied (401) on token.html retrieval");
} }
if (response.getStatusLine().getStatusCode() == 404) { if (response.getStatusLine().getStatusCode() == 404) {
throw new DaemonException(ExceptionType.ConnectionError, "Not found (404); server doesn't exist or is inaccessible"); throw new DaemonException(ExceptionType.ConnectionError,
"Not found (404); server doesn't exist or is inaccessible");
} }
InputStream instream = response.getEntity().getContent(); InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream); String result = HttpHelper.convertStreamToString(instream);
authtoken = result.replaceAll("\\<.*?>","").trim(); authtoken = result.replaceAll("<.*?>", "").trim();
} }
} }
public JSONObject uploadTorrentFile(String file) throws DaemonException, ClientProtocolException, IOException, JSONException { public JSONObject uploadTorrentFile(String file) throws DaemonException, IOException, JSONException {
// Initialise the HTTP client // Initialise the HTTP client
if (httpclient == null) { if (httpclient == null) {
@ -374,7 +411,7 @@ public class UtorrentAdapter implements IDaemonAdapter {
// Build and make request // Build and make request
HttpPost httppost = new HttpPost(buildWebUIUrl() + "?token=" + authtoken + "&action=add-file"); HttpPost httppost = new HttpPost(buildWebUIUrl() + "?token=" + authtoken + "&action=add-file");
File upload = new File(URI.create(file)); File upload = new File(URI.create(file));
Part[] parts = { new FilePart("torrent_file", upload, FilePart.DEFAULT_CONTENT_TYPE, null) }; Part[] parts = {new FilePart("torrent_file", upload, FilePart.DEFAULT_CONTENT_TYPE, null)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
HttpResponse response = httpclient.execute(httppost); HttpResponse response = httpclient.execute(httppost);
@ -389,7 +426,6 @@ public class UtorrentAdapter implements IDaemonAdapter {
/** /**
* Instantiates an HTTP client with proper credentials that can be used for all Transmission requests. * Instantiates an HTTP client with proper credentials that can be used for all Transmission requests.
* @param connectionTimeout The connection timeout in milliseconds
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {
@ -401,7 +437,8 @@ public class UtorrentAdapter implements IDaemonAdapter {
* @return The URL of the RPC API * @return The URL of the RPC API
*/ */
private String buildWebUIUrl() { private String buildWebUIUrl() {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + "/gui/"; return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
"/gui/";
} }
private TorrentStatus convertUtorrentStatus(int uStatus, boolean finished) { private TorrentStatus convertUtorrentStatus(int uStatus, boolean finished) {
@ -460,27 +497,6 @@ public class UtorrentAdapter implements IDaemonAdapter {
} }
} }
// These are the positions inside the JSON response array of a torrent
// See http://forum.utorrent.com/viewtopic.php?id=25661
private static final int RPC_HASH_IDX = 0;
private static final int RPC_STATUS_IDX = 1;
private static final int RPC_NAME_IDX = 2;
private static final int RPC_SIZE_IDX = 3;
private static final int RPC_PARTDONE = 4;
private static final int RPC_DOWNLOADED_IDX = 5;
private static final int RPC_UPLOADED_IDX = 6;
private static final int RPC_DOWNLOADSPEED_IDX = 9;
private static final int RPC_UPLOADSPEED_IDX = 8;
private static final int RPC_ETA_IDX = 10;
private static final int RPC_LABEL_IDX = 11;
private static final int RPC_PEERSCONNECTED_IDX = 12;
private static final int RPC_PEERSINSWARM_IDX = 13;
private static final int RPC_SEEDSCONNECTED_IDX = 14;
private static final int RPC_SEEDSINSWARM_IDX = 15;
private static final int RPC_AVAILABILITY_IDX = 16;
private static final int RPC_ADDEDON_IDX = 23;
private static final int RPC_COMPLETEDON_IDX = 24;
private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONArray results) throws JSONException { private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONArray results) throws JSONException {
// Parse response // Parse response
@ -490,7 +506,7 @@ public class UtorrentAdapter implements IDaemonAdapter {
JSONArray tor = results.getJSONArray(i); JSONArray tor = results.getJSONArray(i);
String name = tor.getString(RPC_NAME_IDX); String name = tor.getString(RPC_NAME_IDX);
boolean downloaded = (tor.getLong(RPC_PARTDONE) == 1000l); boolean downloaded = (tor.getLong(RPC_PARTDONE) == 1000l);
float available = ((float)tor.getInt(RPC_AVAILABILITY_IDX)) / 65536f; // Integer in 1/65536ths float available = ((float) tor.getInt(RPC_AVAILABILITY_IDX)) / 65536f; // Integer in 1/65536ths
// The full torrent path is not available in uTorrent web UI API // The full torrent path is not available in uTorrent web UI API
// Guess the torrent's directory based on the user-specific default download dir and the torrent name // Guess the torrent's directory based on the user-specific default download dir and the torrent name
String dir = null; String dir = null;
@ -505,32 +521,19 @@ public class UtorrentAdapter implements IDaemonAdapter {
TorrentStatus status = convertUtorrentStatus(tor.getInt(RPC_STATUS_IDX), downloaded); TorrentStatus status = convertUtorrentStatus(tor.getInt(RPC_STATUS_IDX), downloaded);
long addedOn = tor.optInt(RPC_ADDEDON_IDX, -1); long addedOn = tor.optInt(RPC_ADDEDON_IDX, -1);
long completedOn = tor.optInt(RPC_COMPLETEDON_IDX, -1); long completedOn = tor.optInt(RPC_COMPLETEDON_IDX, -1);
Date addedOnDate = addedOn == -1? null: new Date(addedOn * 1000L); Date addedOnDate = addedOn == -1 ? null : new Date(addedOn * 1000L);
Date completedOnDate = completedOn == -1? null: new Date(completedOn * 1000L); Date completedOnDate = completedOn == -1 ? null : new Date(completedOn * 1000L);
torrents.add(new Torrent( torrents.add(new Torrent(i, // No ID but a hash is used
i, // No ID but a hash is used tor.getString(RPC_HASH_IDX), name, status, dir, tor.getInt(RPC_DOWNLOADSPEED_IDX),
tor.getString(RPC_HASH_IDX), tor.getInt(RPC_UPLOADSPEED_IDX), tor.getInt(RPC_SEEDSCONNECTED_IDX),
name, tor.getInt(RPC_SEEDSINSWARM_IDX), tor.getInt(RPC_PEERSCONNECTED_IDX),
status, tor.getInt(RPC_PEERSINSWARM_IDX), tor.getInt(RPC_ETA_IDX), tor.getLong(RPC_DOWNLOADED_IDX),
dir, tor.getLong(RPC_UPLOADED_IDX), tor.getLong(RPC_SIZE_IDX),
tor.getInt(RPC_DOWNLOADSPEED_IDX),
tor.getInt(RPC_UPLOADSPEED_IDX),
tor.getInt(RPC_SEEDSCONNECTED_IDX),
tor.getInt(RPC_SEEDSINSWARM_IDX),
tor.getInt(RPC_PEERSCONNECTED_IDX),
tor.getInt(RPC_PEERSINSWARM_IDX),
tor.getInt(RPC_ETA_IDX),
tor.getLong(RPC_DOWNLOADED_IDX),
tor.getLong(RPC_UPLOADED_IDX),
tor.getLong(RPC_SIZE_IDX),
((float) tor.getLong(RPC_PARTDONE)) / 1000f, // Integer in promille ((float) tor.getLong(RPC_PARTDONE)) / 1000f, // Integer in promille
Math.min(available, 1f), // Can be > 100% if multiple peers have 100% Math.min(available, 1f), // Can be > 100% if multiple peers have 100%
tor.getString(RPC_LABEL_IDX).trim(), tor.getString(RPC_LABEL_IDX).trim(), addedOnDate, completedOnDate,
addedOnDate,
completedOnDate,
// uTorrent doesn't give the error message, so just remind that there is some error // uTorrent doesn't give the error message, so just remind that there is some error
status == TorrentStatus.Error? "See GUI for error message": null, status == TorrentStatus.Error ? "See GUI for error message" : null, settings.getType()));
settings.getType()));
} }
return torrents; return torrents;
@ -559,27 +562,23 @@ public class UtorrentAdapter implements IDaemonAdapter {
} }
// These are the positions inside the JSON response array of a torrent
// See http://forum.utorrent.com/viewtopic.php?id=25661
private static final int RPC_FILENAME_IDX = 0;
private static final int RPC_FILESIZE_IDX = 1;
private static final int RPC_FILEDOWNLOADED_IDX = 2;
private static final int RPC_FILEPRIORITY_IDX = 3;
private ArrayList<TorrentFile> parseJsonFileListing(JSONArray results, Torrent torrent) throws JSONException { private ArrayList<TorrentFile> parseJsonFileListing(JSONArray results, Torrent torrent) throws JSONException {
// Parse response // Parse response
ArrayList<TorrentFile> files = new ArrayList<TorrentFile>(); ArrayList<TorrentFile> files = new ArrayList<TorrentFile>();
boolean createPaths = torrent != null && torrent.getLocationDir() != null && !torrent.getLocationDir().equals(""); boolean createPaths =
torrent != null && torrent.getLocationDir() != null && !torrent.getLocationDir().equals("");
final String pathSep = settings.getOS().getPathSeperator(); final String pathSep = settings.getOS().getPathSeperator();
for (int i = 0; i < results.length(); i++) { for (int i = 0; i < results.length(); i++) {
JSONArray file = results.getJSONArray(i); JSONArray file = results.getJSONArray(i);
// Add the parsed torrent to the list // Add the parsed torrent to the list
files.add(new TorrentFile( files.add(new TorrentFile("" + i, file.getString(RPC_FILENAME_IDX), // Name
"" + i, (createPaths ?
file.getString(RPC_FILENAME_IDX), // Name file.getString(RPC_FILENAME_IDX).replace((pathSep.equals("/") ? "\\" : "/"), pathSep) :
(createPaths? file.getString(RPC_FILENAME_IDX).replace((pathSep.equals("/")? "\\": "/"), pathSep): null), // Relative path; 'wrong' path slashes will be replaced null), // Relative path; 'wrong' path slashes will be replaced
(createPaths? torrent.getLocationDir() + file.getString(RPC_FILENAME_IDX).replace((pathSep.equals("/")? "\\": "/"), pathSep): null), // Full path; 'wrong' path slashes will be replaced (createPaths ? torrent.getLocationDir() +
file.getString(RPC_FILENAME_IDX).replace((pathSep.equals("/") ? "\\" : "/"), pathSep) :
null), // Full path; 'wrong' path slashes will be replaced
file.getLong(RPC_FILESIZE_IDX), // Total size file.getLong(RPC_FILESIZE_IDX), // Total size
file.getLong(RPC_FILEDOWNLOADED_IDX), // Part done file.getLong(RPC_FILEDOWNLOADED_IDX), // Part done
convertUtorrentPriority(file.getInt(RPC_FILEPRIORITY_IDX)))); // Priority convertUtorrentPriority(file.getInt(RPC_FILEPRIORITY_IDX)))); // Priority
@ -588,10 +587,10 @@ public class UtorrentAdapter implements IDaemonAdapter {
} }
private String getAllHashes() throws DaemonException, JSONException { private String getAllHashes(Log log) throws DaemonException, JSONException {
// Make a retrieve torrents call first to gather all hashes // Make a retrieve torrents call first to gather all hashes
JSONObject result = makeUtorrentRequest("&list=1"); JSONObject result = makeUtorrentRequest(log, "&list=1");
ArrayList<Torrent> torrents = parseJsonRetrieveTorrents(result.getJSONArray("torrents")); ArrayList<Torrent> torrents = parseJsonRetrieveTorrents(result.getJSONArray("torrents"));
// Build a string of hashes of all the torrents // Build a string of hashes of all the torrents

28
app/src/main/java/org/transdroid/daemon/Vuze/VuzeAdapter.java

@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.openjpa.lib.util.Base16Encoder; import org.apache.openjpa.lib.util.Base16Encoder;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
@ -52,7 +53,6 @@ import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog;
/** /**
* An adapter that allows for easy access to Vuze torrent data. Communication * An adapter that allows for easy access to Vuze torrent data. Communication
@ -81,14 +81,14 @@ public class VuzeAdapter implements IDaemonAdapter {
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
Object result = makeVuzeCall(DaemonMethod.Retrieve, "getDownloads"); Object result = makeVuzeCall(DaemonMethod.Retrieve, "getDownloads");
return new RetrieveTaskSuccessResult((RetrieveTask) task, onTorrentsRetrieved(result),null); return new RetrieveTaskSuccessResult((RetrieveTask) task, onTorrentsRetrieved(log, result),null);
case GetFileList: case GetFileList:
@ -189,8 +189,10 @@ public class VuzeAdapter implements IDaemonAdapter {
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
makeVuzeCall(DaemonMethod.SetTransferRates, "setBooleanParameter[String,boolean]", new Object[] { "Auto Upload Speed Enabled", false } ); makeVuzeCall(DaemonMethod.SetTransferRates, "setBooleanParameter[String,boolean]", new Object[] { "Auto Upload Speed Enabled", false } );
makeVuzeCall(DaemonMethod.SetTransferRates, "setCoreIntParameter[String,int]", new Object[] { "Max Upload Speed KBs", (ratesTask.getUploadRate() == null? 0: ratesTask.getUploadRate().intValue())} ); makeVuzeCall(DaemonMethod.SetTransferRates, "setCoreIntParameter[String,int]", new Object[] { "Max Upload Speed KBs", (ratesTask.getUploadRate() == null? 0:
makeVuzeCall(DaemonMethod.SetTransferRates, "setCoreIntParameter[String,int]", new Object[] { "Max Download Speed KBs", (ratesTask.getDownloadRate() == null? 0: ratesTask.getDownloadRate().intValue())} ); ratesTask.getUploadRate())} );
makeVuzeCall(DaemonMethod.SetTransferRates, "setCoreIntParameter[String,int]", new Object[] { "Max Download Speed KBs", (ratesTask.getDownloadRate() == null? 0:
ratesTask.getDownloadRate())} );
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
@ -318,7 +320,7 @@ public class VuzeAdapter implements IDaemonAdapter {
Map<String, Object> torrentData = rpcclient.callXMLRPC(vuzeObjectID, serverMethod, params, savedConnectionID, false); Map<String, Object> torrentData = rpcclient.callXMLRPC(vuzeObjectID, serverMethod, params, savedConnectionID, false);
serverMethod = "addDownload[Torrent]"; serverMethod = "addDownload[Torrent]";
vuzeObjectID = savedDownloadManagerID; vuzeObjectID = savedDownloadManagerID;
params = new String[] { ((Long) torrentData.get("_object_id")).toString() }; params = new String[] { torrentData.get("_object_id").toString() };
paramsAreVuzeObjects = true; paramsAreVuzeObjects = true;
} }
@ -346,7 +348,7 @@ public class VuzeAdapter implements IDaemonAdapter {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private List<Torrent> onTorrentsRetrieved(Object result) throws DaemonException { private List<Torrent> onTorrentsRetrieved(Log log, Object result) throws DaemonException {
Map<String, Object> response = (Map<String, Object>) result; Map<String, Object> response = (Map<String, Object>) result;
@ -355,7 +357,7 @@ public class VuzeAdapter implements IDaemonAdapter {
return new ArrayList<Torrent>(); return new ArrayList<Torrent>();
} }
DLog.d(LOG_NAME, response.toString().length() > 300? response.toString().substring(0, 300) + "... (" + response.toString().length() + " chars)": response.toString()); log.d(LOG_NAME, response.toString().length() > 300? response.toString().substring(0, 300) + "... (" + response.toString().length() + " chars)": response.toString());
List<Torrent> torrents = new ArrayList<Torrent>(); List<Torrent> torrents = new ArrayList<Torrent>();
@ -376,7 +378,7 @@ public class VuzeAdapter implements IDaemonAdapter {
} }
*/ */
Map<String, Object> info = (Map<String, Object>) response.get(key); Map<String, Object> info = (Map<String, Object>) response.get(key);
if (info == null || !info.containsKey("_object_id") || ((Long)info.get("_object_id")) == null) { if (info == null || !info.containsKey("_object_id") || info.get("_object_id") == null) {
// No valid XML data object returned // No valid XML data object returned
throw new DaemonException(DaemonException.ExceptionType.UnexpectedResponse, "Map of objects returned by Vuze, but these object do not have some <info> attached or no <_object_id> is available"); throw new DaemonException(DaemonException.ExceptionType.UnexpectedResponse, "Map of objects returned by Vuze, but these object do not have some <info> attached or no <_object_id> is available");
} }
@ -396,10 +398,10 @@ public class VuzeAdapter implements IDaemonAdapter {
torrents.add(new Torrent( torrents.add(new Torrent(
(Long) info.get("_object_id"), // id (Long) info.get("_object_id"), // id
((Long) info.get("_object_id")).toString(), // hash //(String) torrentinfo.get("hash"), // hash info.get("_object_id").toString(), // hash //(String) torrentinfo.get("hash"), // hash
(String) info.get("name").toString().trim(), // name info.get("name").toString().trim(), // name
convertTorrentStatus((Long) info.get("state")), // status convertTorrentStatus((Long) info.get("state")), // status
(String) statsinfo.get("target_file_or_dir") + "/", // locationDir statsinfo.get("target_file_or_dir") + "/", // locationDir
rateDownload, // rateDownload rateDownload, // rateDownload
((Long)statsinfo.get("upload_average")).intValue(), // rateUpload ((Long)statsinfo.get("upload_average")).intValue(), // rateUpload
announceSeedCount, // seedersConnected announceSeedCount, // seedersConnected
@ -461,7 +463,7 @@ public class VuzeAdapter implements IDaemonAdapter {
String file = (String)info.get("file"); String file = (String)info.get("file");
files.add(new TorrentFile( files.add(new TorrentFile(
"" + (Long)info.get("_object_id"), String.valueOf(info.get("_object_id")),
new File(file).getName(), // name new File(file).getName(), // name
(file.length() > torrent.getLocationDir().length()? file.substring(torrent.getLocationDir().length()): file), // name (file.length() > torrent.getLocationDir().length()? file.substring(torrent.getLocationDir().length()): file), // name
file, // fullPath file, // fullPath

66
app/src/main/java/org/transdroid/daemon/task/DaemonTask.java

@ -15,39 +15,44 @@
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>. * along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package org.transdroid.daemon.task; package org.transdroid.daemon.task;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonMethod; import org.transdroid.daemon.DaemonMethod;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
/** /**
* A daemon task represents some action that needs to be performed * A daemon task represents some action that needs to be performed on the server daemon. It has no capabilities on
* on the server daemon. It has no capabilities on itself; these are * itself; these are marshaled to the daemon adapter. Therefore all needed info (the parameters) needs to be added to
* marshaled to the daemon adapter. Therefore all needed info (the * the extras bundle.
* parameters) needs to be added to the extras bundle. * <p/>
* * To help create these tasks and there data, each possible daemon method is created using a task-specific separate
* To help create these tasks and there data, each possible daemon * class with a create() method.
* method is created using a task-specific separate class with a * <p/>
* create() method. * This class is Parcelable so it can be persisted in between an Activity breakdown and recreation.
*
* This class is Parcelable so it can be persisted in between an
* Activity breakdown and recreation.
*
* @author erickok * @author erickok
*
*/ */
public class DaemonTask implements Parcelable { public class DaemonTask implements Parcelable {
protected IDaemonAdapter adapter; public static final Parcelable.Creator<DaemonTask> CREATOR = new Parcelable.Creator<DaemonTask>() {
public DaemonTask createFromParcel(Parcel in) {
return new DaemonTask(in);
}
public DaemonTask[] newArray(int size) {
return new DaemonTask[size];
}
};
protected final DaemonMethod method; protected final DaemonMethod method;
protected final Torrent targetTorrent; protected final Torrent targetTorrent;
protected final Bundle extras; protected final Bundle extras;
protected IDaemonAdapter adapter;
private DaemonTask(Parcel in) { private DaemonTask(Parcel in) {
this.method = DaemonMethod.getStatus(in.readInt()); this.method = DaemonMethod.getStatus(in.readInt());
@ -68,9 +73,10 @@ public class DaemonTask implements Parcelable {
/** /**
* Execute the task on the appropriate daemon adapter * Execute the task on the appropriate daemon adapter
* @param log The logger to use when writing exceptions and debug information
*/ */
public DaemonTaskResult execute() { public DaemonTaskResult execute(Log log) {
return adapter.executeTask(this); return adapter.executeTask(log, this);
} }
public DaemonMethod getMethod() { public DaemonMethod getMethod() {
@ -89,16 +95,6 @@ public class DaemonTask implements Parcelable {
return extras; return extras;
} }
public static final Parcelable.Creator<DaemonTask> CREATOR = new Parcelable.Creator<DaemonTask>() {
public DaemonTask createFromParcel(Parcel in) {
return new DaemonTask(in);
}
public DaemonTask[] newArray(int size) {
return new DaemonTask[size];
}
};
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
@ -112,10 +108,14 @@ public class DaemonTask implements Parcelable {
} }
/** /**
* Returns a readable description of this task in the form 'MethodName on AdapterName with TorrentName and AllExtras' * Returns a readable description of this task in the form 'MethodName on AdapterName with TorrentName and
* AllExtras'
*/ */
public String toString() { public String toString() {
return method.toString() + (adapter == null? "": " on " + adapter.getType()) + (targetTorrent != null || extras != null? " with ": "") + (targetTorrent == null? "": targetTorrent.toString() + (targetTorrent != null && extras != null? " and ": "")) + (extras == null? "": extras.toString()); return method.toString() + (adapter == null ? "" : " on " + adapter.getType()) +
(targetTorrent != null || extras != null ? " with " : "") +
(targetTorrent == null ? "" : targetTorrent.toString() + (extras != null ? " and " : "")) +
(extras == null ? "" : extras.toString());
} }
} }

7
app/src/main/java/org/transdroid/daemon/util/Collections2.java

@ -1,7 +1,5 @@
package org.transdroid.daemon.util; package org.transdroid.daemon.util;
import java.util.Iterator;
/** /**
* Helpers on Collections * Helpers on Collections
*/ */
@ -13,9 +11,8 @@ public class Collections2 {
public static <T> String joinString(Iterable<T> iterable, String separator) { public static <T> String joinString(Iterable<T> iterable, String separator) {
boolean first = true; boolean first = true;
String result = ""; String result = "";
Iterator<T> it = iterable.iterator(); for (T anIterable : iterable) {
while (it.hasNext()) { result += (first ? "" : separator) + anIterable.toString();
result += (first ? "" : separator) + it.next().toString();
first = false; first = false;
} }
return result; return result;

59
app/src/main/java/org/transdroid/daemon/util/DLog.java

@ -1,59 +0,0 @@
/*
* This file is part of Transdroid <http://www.transdroid.org>
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.transdroid.daemon.util;
/**
* Universal logger; applications using this library should
* attach an ITLogger using <code>setLogger(ITLogger)</code>
* to receive any logging information from the daemons.
*
* @author erickok
*/
public class DLog {
private static final String LOG_TAG = "Transdroid";
private static ITLogger instance = null;
public static void setLogger(ITLogger logger) {
instance = logger;
}
/**
* Send a DEBUG log message.
* @param self Unique source tag, identifying the part of Transdroid it happens in
* @param msg The debug message to log
*/
public static void d(String self, String msg) {
if (instance != null) {
instance.d(LOG_TAG, self + ": " + msg);
}
}
/**
* Send an ERROR log message.
* @param self Unique source tag, identifying the part of Transdroid it happens in
* @param msg The error message to log
*/
public static void e(String self, String msg) {
if (instance != null) {
instance.e(LOG_TAG, self + ": " + msg);
}
}
}

154
app/src/main/java/org/transdroid/daemon/util/HttpHelper.java

@ -17,15 +17,7 @@
*/ */
package org.transdroid.daemon.util; package org.transdroid.daemon.util;
import java.io.BufferedReader; import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HeaderElement; import org.apache.http.HeaderElement;
@ -53,7 +45,15 @@ import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import android.net.Uri; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
/** /**
* Provides a set of general helper methods that can be used in web-based communication. * Provides a set of general helper methods that can be used in web-based communication.
@ -62,15 +62,41 @@ import android.net.Uri;
public class HttpHelper { public class HttpHelper {
public static final int DEFAULT_CONNECTION_TIMEOUT = 8000; public static final int DEFAULT_CONNECTION_TIMEOUT = 8000;
public static final String SCHEME_HTTP = "http";
public static final String SCHEME_HTTPS = "https";
public static final String SCHEME_MAGNET = "magnet";
public static final String SCHEME_FILE = "file";
/** /**
* The 'User-Agent' name to send to the server * The 'User-Agent' name to send to the server
*/ */
public static String userAgent = "Transdroid Torrent Connect"; public static String userAgent = "Transdroid Torrent Connect";
/**
* HTTP request interceptor to allow for GZip-encoded data transfer
*/
public static HttpRequestInterceptor gzipRequestInterceptor = new HttpRequestInterceptor() {
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
if (!request.containsHeader("Accept-Encoding")) {
request.addHeader("Accept-Encoding", "gzip");
}
}
};
/**
* HTTP response interceptor that decodes GZipped data
*/
public static HttpResponseInterceptor gzipResponseInterceptor = new HttpResponseInterceptor() {
public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException {
HttpEntity entity = response.getEntity();
Header ceheader = entity.getContentEncoding();
if (ceheader != null) {
HeaderElement[] codecs = ceheader.getElements();
for (HeaderElement codec : codecs) {
if (codec.getName().equalsIgnoreCase("gzip")) {
response.setEntity(new GzipDecompressingEntity(response.getEntity()));
return;
}
}
}
}
};
/** /**
* Creates a standard Apache HttpClient that is thread safe, supports different SSL auth methods and basic * Creates a standard Apache HttpClient that is thread safe, supports different SSL auth methods and basic
@ -98,8 +124,8 @@ public class HttpHelper {
* @throws DaemonException Thrown when information (such as username/password) is missing * @throws DaemonException Thrown when information (such as username/password) is missing
*/ */
public static DefaultHttpClient createStandardHttpClient(boolean userBasicAuth, String username, String password, public static DefaultHttpClient createStandardHttpClient(boolean userBasicAuth, String username, String password,
boolean sslTrustAll, String sslTrustKey, int timeout, String authAddress, int authPort) boolean sslTrustAll, String sslTrustKey, int timeout,
throws DaemonException { String authAddress, int authPort) throws DaemonException {
// Register http and https sockets // Register http and https sockets
SchemeRegistry registry = new SchemeRegistry(); SchemeRegistry registry = new SchemeRegistry();
@ -122,8 +148,8 @@ public class HttpHelper {
HttpProtocolParams.setUserAgent(httpparams, userAgent); HttpProtocolParams.setUserAgent(httpparams, userAgent);
} }
DefaultHttpClient httpclient = new DefaultHttpClient(new ThreadSafeClientConnManager(httpparams, registry), DefaultHttpClient httpclient =
httpparams); new DefaultHttpClient(new ThreadSafeClientConnManager(httpparams, registry), httpparams);
// Authentication credentials // Authentication credentials
if (userBasicAuth) { if (userBasicAuth) {
@ -131,8 +157,8 @@ public class HttpHelper {
throw new DaemonException(ExceptionType.AuthenticationFailure, throw new DaemonException(ExceptionType.AuthenticationFailure,
"No username or password was provided while we had authentication enabled"); "No username or password was provided while we had authentication enabled");
} }
httpclient.getCredentialsProvider().setCredentials( httpclient.getCredentialsProvider()
new AuthScope(authAddress, authPort, AuthScope.ANY_REALM), .setCredentials(new AuthScope(authAddress, authPort, AuthScope.ANY_REALM),
new UsernamePasswordCredentials(username, password)); new UsernamePasswordCredentials(username, password));
} }
@ -140,64 +166,6 @@ public class HttpHelper {
} }
/**
* HTTP request interceptor to allow for GZip-encoded data transfer
*/
public static HttpRequestInterceptor gzipRequestInterceptor = new HttpRequestInterceptor() {
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
if (!request.containsHeader("Accept-Encoding")) {
request.addHeader("Accept-Encoding", "gzip");
}
}
};
/**
* HTTP response interceptor that decodes GZipped data
*/
public static HttpResponseInterceptor gzipResponseInterceptor = new HttpResponseInterceptor() {
public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException {
HttpEntity entity = response.getEntity();
Header ceheader = entity.getContentEncoding();
if (ceheader != null) {
HeaderElement[] codecs = ceheader.getElements();
for (int i = 0; i < codecs.length; i++) {
if (codecs[i].getName().equalsIgnoreCase("gzip")) {
response.setEntity(new HttpHelper.GzipDecompressingEntity(response.getEntity()));
return;
}
}
}
}
};
/**
* HTTP entity wrapper to decompress GZipped HTTP responses
*/
private static class GzipDecompressingEntity extends HttpEntityWrapper {
public GzipDecompressingEntity(final HttpEntity entity) {
super(entity);
}
@Override
public InputStream getContent() throws IOException, IllegalStateException {
// the wrapped entity's getContent() decides about repeatability
InputStream wrappedin = wrappedEntity.getContent();
return new GZIPInputStream(wrappedin);
}
@Override
public long getContentLength() {
// length of ungzipped content is not known
return -1;
}
}
/* /*
* To convert the InputStream to String we use the BufferedReader.readLine() method. We iterate until the * To convert the InputStream to String we use the BufferedReader.readLine() method. We iterate until the
* BufferedReader return null which means there's no more data to read. Each line will appended to a StringBuilder * BufferedReader return null which means there's no more data to read. Each line will appended to a StringBuilder
@ -215,10 +183,10 @@ public class HttpHelper {
BufferedReader reader = new BufferedReader(isr); BufferedReader reader = new BufferedReader(isr);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String line = null; String line;
try { try {
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
sb.append(line + "\n"); sb.append(line).append("\n");
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -272,4 +240,30 @@ public class HttpHelper {
} }
/**
* HTTP entity wrapper to decompress GZipped HTTP responses
*/
private static class GzipDecompressingEntity extends HttpEntityWrapper {
public GzipDecompressingEntity(final HttpEntity entity) {
super(entity);
}
@Override
public InputStream getContent() throws IOException, IllegalStateException {
// the wrapped entity's getContent() decides about repeatability
InputStream wrappedin = wrappedEntity.getContent();
return new GZIPInputStream(wrappedin);
}
@Override
public long getContentLength() {
// length of ungzipped content is not known
return -1;
}
}
} }

44
app/src/main/java/org/transdroid/daemon/util/ITLogger.java

@ -1,44 +0,0 @@
/*
* This file is part of Transdroid <http://www.transdroid.org>
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.transdroid.daemon.util;
/**
* Interface that should be implemented for any logging
* information to get from the daemons. Applications using
* this library should attach an instance using
* <code>TLog.setLogger(ITLogger)</code>
*
* @author erickok
*/
public interface ITLogger {
/**
* Send a DEBUG log message.
* @param self Unique source tag, identifying the part of Transdroid it happens in
* @param msg The debug message to log
*/
public abstract void d(String self, String msg);
/**
* Send an ERROR log message.
* @param self Unique source tag, identifying the part of Transdroid it happens in
* @param msg The error message to log
*/
public abstract void e(String self, String msg);
}

77
app/src/main/java/org/transdroid/daemon/util/Pair.java

@ -1,77 +0,0 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.transdroid.daemon.util;
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
* implementation of equals(), returning true if equals() is true on each of the contained
* objects.
*/
public class Pair<F, S> {
public final F first;
public final S second;
/**
* Constructor for a Pair. If either are null then equals() and hashCode() will throw
* a NullPointerException.
* @param first the first object in the Pair
* @param second the second object in the pair
*/
public Pair(F first, S second) {
this.first = first;
this.second = second;
}
/**
* Checks the two objects for equality by delegating to their respective equals() methods.
* @param o the Pair to which this one is to be checked for equality
* @return true if the underlying objects of the Pair are both considered equals()
*/
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Pair)) return false;
final Pair<F, S> other;
try {
other = (Pair<F, S>) o;
} catch (ClassCastException e) {
return false;
}
return first.equals(other.first) && second.equals(other.second);
}
/**
* Compute a hash code using the hash codes of the underlying objects
* @return a hashcode of the Pair
*/
public int hashCode() {
int result = 17;
result = 31 * result + first.hashCode();
result = 31 * result + second.hashCode();
return result;
}
/**
* Convenience method for creating an appropriately typed pair.
* @param a the first object in the Pair
* @param b the second object in the pair
* @return a Pair that is templatized with the types of a and b
*/
public static <A, B> Pair <A, B> create(A a, B b) {
return new Pair<A, B>(a, b);
}
}

15
app/src/main/java/org/transdroid/daemon/util/SelfSignedTrustManager.java

@ -27,7 +27,6 @@ import javax.net.ssl.X509TrustManager;
public class SelfSignedTrustManager implements X509TrustManager { public class SelfSignedTrustManager implements X509TrustManager {
private static final X509Certificate[] acceptedIssuers = new X509Certificate[]{}; private static final X509Certificate[] acceptedIssuers = new X509Certificate[]{};
private static final String LOG_NAME = "TrustManager";
private String certKey = null; private String certKey = null;
@ -50,9 +49,9 @@ public class SelfSignedTrustManager implements X509TrustManager {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
StringBuffer buf = new StringBuffer(bytes.length * 2); StringBuffer buf = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; ++i) { for (byte aByte : bytes) {
buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]); buf.append(hexDigits[(aByte & 0xf0) >> 4]);
buf.append(hexDigits[bytes[i] & 0x0f]); buf.append(hexDigits[aByte & 0x0f]);
} }
return buf.toString(); return buf.toString();
@ -75,15 +74,15 @@ public class SelfSignedTrustManager implements X509TrustManager {
// Assume self-signed root is okay? // Assume self-signed root is okay?
X509Certificate sslCert = chain[0]; X509Certificate sslCert = chain[0];
String thumbprint = SelfSignedTrustManager.getThumbPrint(sslCert); String thumbprint = SelfSignedTrustManager.getThumbPrint(sslCert);
DLog.d(LOG_NAME, thumbprint);
if (ourKey.equalsIgnoreCase(thumbprint)) { if (ourKey.equalsIgnoreCase(thumbprint)) {
return; return;
} else { }
CertificateException certificateException = CertificateException certificateException =
new CertificateException("Certificate key [" + thumbprint + "] doesn't match expected value."); new CertificateException("Certificate key [" + thumbprint + "] doesn't match expected value.");
DLog.e(SelfSignedTrustManager.class.getSimpleName(), certificateException.toString()); //Log.e(SelfSignedTrustManager.class.getSimpleName(), certificateException.toString());
throw certificateException; throw certificateException;
}
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new CertificateException("Unable to check self-signed cert, unknown algorithm. " + e.toString()); throw new CertificateException("Unable to check self-signed cert, unknown algorithm. " + e.toString());
} }

3
app/src/main/java/org/transdroid/daemon/util/TlsSniSocketFactory.java

@ -19,6 +19,7 @@ package org.transdroid.daemon.util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.net.SSLCertificateSocketFactory; import android.net.SSLCertificateSocketFactory;
import android.os.Build; import android.os.Build;
import android.util.Log;
import org.apache.http.conn.scheme.LayeredSocketFactory; import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.apache.http.conn.ssl.StrictHostnameVerifier;
@ -130,7 +131,7 @@ public class TlsSniSocketFactory implements LayeredSocketFactory {
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class); java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(ssl, host); setHostnameMethod.invoke(ssl, host);
} catch (Exception e) { } catch (Exception e) {
DLog.d(TlsSniSocketFactory.class.getSimpleName(), "SNI not usable: " + e); Log.d(TlsSniSocketFactory.class.getSimpleName(), "SNI not usable: " + e);
} }
} }

Loading…
Cancel
Save