Browse Source

Hack-y fixes #115 by manually stripping out decimal separators to parse numbers in a somewhat reliable way. Should work with most locales. Also party fixes #102 or at least until the real date done is given by the qBittorrent web interface.

pull/148/merge
Eric Kok 10 years ago
parent
commit
65ed3761fd
  1. 189
      lib/src/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java
  2. 4
      lib/src/org/transdroid/daemon/Torrent.java

189
lib/src/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java

@ -70,9 +70,7 @@ import com.android.internalcopy.http.multipart.Part;
/** /**
* The daemon adapter for the qBittorrent torrent client. * The daemon adapter for the qBittorrent torrent client.
*
* @author erickok * @author erickok
*
*/ */
public class QbittorrentAdapter implements IDaemonAdapter { public class QbittorrentAdapter implements IDaemonAdapter {
@ -81,11 +79,11 @@ public class QbittorrentAdapter implements IDaemonAdapter {
private DaemonSettings settings; private DaemonSettings settings;
private DefaultHttpClient httpclient; private DefaultHttpClient httpclient;
private int version = -1; private int version = -1;
public QbittorrentAdapter(DaemonSettings settings) { public QbittorrentAdapter(DaemonSettings settings) {
this.settings = settings; this.settings = settings;
} }
private synchronized void ensureVersion() throws DaemonException { private synchronized void ensureVersion() throws DaemonException {
if (version > 0) if (version > 0)
return; return;
@ -129,51 +127,52 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// 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
version = 10000; version = 10000;
} }
@Override @Override
public DaemonTaskResult executeTask(DaemonTask task) { public DaemonTaskResult executeTask(DaemonTask task) {
try { try {
ensureVersion(); ensureVersion();
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(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 = ((GetTorrentDetailsTask) task).getTargetTorrent().getUniqueID();
JSONArray messages = new JSONArray(makeRequest("/json/propertiesTrackers/" + mhash)); JSONArray messages = new JSONArray(makeRequest("/json/propertiesTrackers/" + mhash));
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages)); return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
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 = ((GetFileListTask) task).getTargetTorrent().getUniqueID();
JSONArray files = new JSONArray(makeRequest("/json/propertiesFiles/" + fhash)); JSONArray files = new JSONArray(makeRequest("/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);
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("/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("/command/download", new BasicNameValuePair("urls", magnet));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -181,15 +180,16 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
makeRequest((removeTask.includingData()? "/command/deletePerm": "/command/delete"), new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID())); makeRequest((removeTask.includingData() ? "/command/deletePerm" : "/command/delete"),
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("/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
@ -201,7 +201,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Resume a torrent // Resume a torrent
makeRequest("/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID())); makeRequest("/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
@ -222,27 +222,31 @@ 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().getUniqueID()), new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio)); makeRequest("/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent()
.getUniqueID()), new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair(
"priority", newPrio));
} }
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetTransferRates: case SetTransferRates:
// 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().intValue());
int ul = (ratesTask.getUploadRate() == null? -1: ratesTask.getUploadRate().intValue()); int ul = (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate().intValue());
// First get the preferences // First get the preferences
JSONObject prefs = new JSONObject(makeRequest("/json/preferences")); JSONObject prefs = new JSONObject(makeRequest("/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", new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8))); makeRequest("/command/setPreferences",
new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8)));
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,11 +269,11 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
return makeWebRequest(path, httppost); return makeWebRequest(path, httppost);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
private String makeUploadRequest(String path, String file) throws DaemonException { private String makeUploadRequest(String path, String file) throws DaemonException {
@ -282,17 +286,17 @@ public class QbittorrentAdapter implements IDaemonAdapter {
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(path, httppost);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new DaemonException(ExceptionType.FileAccessError, e.toString()); throw new DaemonException(ExceptionType.FileAccessError, e.toString());
} }
} }
private String makeWebRequest(String path, HttpPost httppost) throws DaemonException { private String makeWebRequest(String path, HttpPost httppost) throws DaemonException {
try { try {
// Initialise the HTTP client // Initialise the HTTP client
if (httpclient == null) { if (httpclient == null) {
initialise(); initialise();
@ -300,17 +304,18 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Execute // Execute
HttpResponse response = httpclient.execute(httppost); HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
if (entity != null) { if (entity != null) {
// 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);
instream.close(); instream.close();
//TLog.d(LOG_NAME, "Success: " + (result.length() > 300? result.substring(0, 300) + "... (" + result.length() + " chars)": result)); // TLog.d(LOG_NAME, "Success: " + (result.length() > 300? result.substring(0, 300) + "... (" +
// result.length() + " chars)": result));
// Return raw result // Return raw result
return result; return result;
} }
@ -322,7 +327,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
DLog.d(LOG_NAME, "Error: " + e.toString()); DLog.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString());
} }
} }
/** /**
@ -331,20 +336,9 @@ public class QbittorrentAdapter implements IDaemonAdapter {
* @throws DaemonException On conflicting or missing settings * @throws DaemonException On conflicting or missing settings
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException {
httpclient = HttpHelper.createStandardHttpClient(settings, true); httpclient = HttpHelper.createStandardHttpClient(settings, true);
/*httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
for (Header header : request.getAllHeaders()) {
TLog.d(LOG_NAME, "Request: " + header.getName() + ": " + header.getValue());
}
}
});*/
} }
/** /**
* Build the URL of the web UI request from the user settings * Build the URL of the web UI request from the user settings
* @return The URL to request * @return The URL to request
@ -352,9 +346,9 @@ public class QbittorrentAdapter implements IDaemonAdapter {
private String buildWebUIUrl(String path) { private String buildWebUIUrl(String path) {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + path; return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + path;
} }
private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException { private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException {
ArrayList<String> trackers = new ArrayList<String>(); ArrayList<String> trackers = new ArrayList<String>();
ArrayList<String> errors = new ArrayList<String>(); ArrayList<String> errors = new ArrayList<String>();
@ -368,14 +362,14 @@ public class QbittorrentAdapter implements IDaemonAdapter {
errors.add(msg); errors.add(msg);
} }
} }
// Return the list // Return the list
return new TorrentDetails(trackers, errors); return new TorrentDetails(trackers, errors);
} }
private ArrayList<Torrent> parseJsonTorrents(JSONArray response) throws JSONException { private ArrayList<Torrent> parseJsonTorrents(JSONArray response) throws JSONException {
// Parse response // Parse response
ArrayList<Torrent> torrents = new ArrayList<Torrent>(); ArrayList<Torrent> torrents = new ArrayList<Torrent>();
for (int i = 0; i < response.length(); i++) { for (int i = 0; i < response.length(); i++) {
@ -387,40 +381,27 @@ public class QbittorrentAdapter implements IDaemonAdapter {
double ratio = parseRatio(tor.getString("ratio")); double ratio = parseRatio(tor.getString("ratio"));
double progress = tor.getDouble("progress"); double progress = tor.getDouble("progress");
int dlspeed = parseSpeed(tor.getString("dlspeed")); int dlspeed = parseSpeed(tor.getString("dlspeed"));
long eta = -1L;
if (dlspeed > 0)
eta = (long) (size - (size * progress)) / dlspeed;
// 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( torrents.add(new Torrent((long) i, tor.getString("hash"), tor.getString("name"), parseStatus(tor
(long)i, .getString("state")), null, dlspeed, parseSpeed(tor.getString("upspeed")), leechers, leechers
tor.getString("hash"), + seeders, known, known, (int) eta, (long) (size * progress), (long) (size * ratio), size,
tor.getString("name"), (float) progress, 0f, null, null, null, null, settings.getType()));
parseStatus(tor.getString("state")),
null,
dlspeed,
parseSpeed(tor.getString("upspeed")),
leechers,
leechers + seeders,
known,
known,
(int) ((size - (size * progress)) / dlspeed),
(long)(size * progress),
(long)(size * ratio),
size,
(float)progress,
0f,
null,
null, // Only available in /json/propertiesGeneral on a per-torrent basis, unfortunately
null,
null,
settings.getType()));
} }
// Return the list // Return the list
return torrents; return torrents;
} }
private double parseRatio(String string) { private double parseRatio(String string) {
// Ratio is given in "1.5" string format // Ratio is given in "1.5" string format
try { try {
// FIXME Hack for issue #115: Strip the possible . and , separators in a hopefully reliable fashion, for now
string = string.replace(",", ".");
return Double.parseDouble(string); return Double.parseDouble(string);
} catch (Exception e) { } catch (Exception e) {
return 0D; return 0D;
@ -431,19 +412,31 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// See https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-Documentation // See https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-Documentation
if (string.equals("Unknown")) if (string.equals("Unknown"))
return -1; return -1;
// Sizes are given in "703.3 MiB"-like string format // Sizes are given in "1,023.3 MiB"-like string format
// Returns size in B-based long // FIXME Hack for issue #115: Strip the possible . and , separators in a hopefully reliable fashion, for now
String[] parts = string.split(" "); String[] parts = string.split(" ");
String part1 = "";
if (parts[0].length() >= 3)
part1 = parts[0].substring(0, parts[0].length() - 3);
String part2 = parts[0].substring(parts[0].length() - 3);
parts[0] = part1.replace("Ê", "").replace(" ", "").replace(",", "").replace(".", "") + part2.replace(",", ".");
// Returns size in B-based long
double number;
try {
number = Double.parseDouble(parts[0]);
} catch (Exception e) {
return -1L;
}
if (parts[1].equals("TiB")) { if (parts[1].equals("TiB")) {
return (long) (Double.parseDouble(parts[0]) * 1024L * 1024L * 1024L * 1024L); return (long) (number * 1024L * 1024L * 1024L * 1024L);
} else if (parts[1].equals("GiB")) { } else if (parts[1].equals("GiB")) {
return (long) (Double.parseDouble(parts[0]) * 1024L * 1024L * 1024L); return (long) (number * 1024L * 1024L * 1024L);
} else if (parts[1].equals("MiB")) { } else if (parts[1].equals("MiB")) {
return (long) (Double.parseDouble(parts[0]) * 1024L * 1024L); return (long) (number * 1024L * 1024L);
} else if (parts[1].equals("KiB")) { } else if (parts[1].equals("KiB")) {
return (long) (Double.parseDouble(parts[0]) * 1024L); return (long) (number * 1024L);
} }
return (long) (Double.parseDouble(parts[0])); return (long) number;
} }
private int parseKnown(String leechs, String seeds) { private int parseKnown(String leechs, String seeds) {
@ -527,25 +520,19 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
private ArrayList<TorrentFile> parseJsonFiles(JSONArray response) throws JSONException { private ArrayList<TorrentFile> parseJsonFiles(JSONArray response) throws JSONException {
// Parse response // Parse response
ArrayList<TorrentFile> torrentfiles = new ArrayList<TorrentFile>(); ArrayList<TorrentFile> torrentfiles = new ArrayList<TorrentFile>();
for (int i = 0; i < response.length(); i++) { for (int i = 0; i < response.length(); i++) {
JSONObject file = response.getJSONObject(i); JSONObject file = response.getJSONObject(i);
long size = parseSize(file.getString("size")); long size = parseSize(file.getString("size"));
torrentfiles.add(new TorrentFile( torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file
"" + i, .getDouble("progress")), parsePriority(file.getInt("priority"))));
file.getString("name"),
null,
null,
size,
(long) (size * file.getDouble("progress")),
parsePriority(file.getInt("priority"))));
} }
// Return the list // Return the list
return torrentfiles; return torrentfiles;
} }
private Priority parsePriority(int priority) { private Priority parsePriority(int priority) {
@ -570,5 +557,5 @@ public class QbittorrentAdapter implements IDaemonAdapter {
public DaemonSettings getSettings() { public DaemonSettings getSettings() {
return this.settings; return this.settings;
} }
} }

4
lib/src/org/transdroid/daemon/Torrent.java

@ -142,12 +142,14 @@ public final class Torrent implements Parcelable, Comparable<Torrent>, Finishabl
if (realDateDone != null) { if (realDateDone != null) {
this.dateDone = realDateDone; this.dateDone = realDateDone;
} else { } else {
if( this.partDone == 1){ //finished but no finished date set so move to bottom of list if (this.partDone == 1) {
// Finished but no finished date: set so move to bottom of list
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.clear(); cal.clear();
cal.set(1900, 12, 31); cal.set(1900, 12, 31);
this.dateDone = cal.getTime(); this.dateDone = cal.getTime();
} else if (eta == -1 || eta == -2) { } else if (eta == -1 || eta == -2) {
// UNknown eta: move to the top of the list
this.dateDone = new Date(Long.MAX_VALUE); this.dateDone = new Date(Long.MAX_VALUE);
} else { } else {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();

Loading…
Cancel
Save