package org.transdroid.daemon.Ktorrent;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
/**
* A Ktorrent-specific parser for it's /data/torrents.xml output.
*
* @author erickok
*
*/
public class StatsParser {
public static List<Torrent> parse(Reader in, String baseDir, String pathSeperator) throws DaemonException, LoggedOutException {
try {
// Use a PullParser to handle XML tags one by one
XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
xpp.setInput(in);
//xpp.setInput(new FileReader("/sdcard/torrents.xml")); // Used for debugging
// Temp variables to load into torrent objects
int id = 0;
String tname = "";
//String hash = "";
TorrentStatus status = TorrentStatus.Unknown;
long down = 0;
long up = 0;
long total = 0;
int downRate = 0;
int upRate = 0;
int seeders = 0;
int seedersTotal = 0;
int leechers = 0;
int leechersTotal = 0;
float progress = 0;
int numFiles = -1;
// Start pulling
List<Torrent> torrents = new ArrayList<Torrent>();
int next = xpp.nextTag();
String name = xpp.getName();
// Check if we had a proper XML result
if (name.equals("html")) {
// Apparently we were returned an HTML page instead of the expected XML
// This happens in particular when we were logged out (because somebody else logged into KTorrent's web interface)
throw new LoggedOutException();
}
while (next != XmlPullParser.END_DOCUMENT) {
if (next == XmlPullParser.END_TAG && name.equals("torrent")) {
// End of a 'transfer' item, add gathered torrent data
torrents.add(new Torrent(
id,
""+id,
tname,
status,
(baseDir == null? null: (numFiles > 0? baseDir + tname + pathSeperator: baseDir)),
downRate,
upRate,
seeders,
seedersTotal,
leechers,
leechersTotal,
(int) (status == TorrentStatus.Downloading? (total - down) / downRate: -1), // eta (in seconds) = (total_size_in_btes - bytes_already_downloaded) / bytes_per_second
down,
up,
total,
progress,
0f,
null, // Not supported in the web interface
null, // Not supported in the web interface
null, // Not supported in the web interface
null, // Not supported in the web interface
Daemon.KTorrent));
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 && name.equals("torrent")){
// Start of a new 'transfer' item; reset gathered torrent data
tname = "";
//hash = "";
status = TorrentStatus.Unknown;
down = 0;
up = 0;
total = 0;
downRate = 0;
upRate = 0;
seeders = 0;
seedersTotal = 0;
leechers = 0;
leechersTotal = 0;
progress = 0;
numFiles = -1;
} else if (next == XmlPullParser.START_TAG){
// Probably encountered a torrent property, i.e. '<status>Stopped</status>'
next = xpp.next();
if (next == XmlPullParser.TEXT) {
if (name.equals("name")) {
tname = xpp.getText().trim();
//} else if (name.equals("info_hash")) {
//hash = xpp.getText().trim();
} else if (name.equals("status")) {
status = convertStatus(xpp.getText());
} else if (name.equals("bytes_downloaded")) {
down = convertSize(xpp.getText());
} else if (name.equals("bytes_uploaded")) {
up = convertSize(xpp.getText());
} else if (name.equals("total_bytes_to_download")) {
total = convertSize(xpp.getText());
} else if (name.equals("download_rate")) {
downRate = convertRate(xpp.getText());
} else if (name.equals("upload_rate")) {
upRate = convertRate(xpp.getText());
} else if (name.equals("seeders")) {
seeders = Integer.parseInt(xpp.getText());
} else if (name.equals("seeders_total")) {
seedersTotal = Integer.parseInt(xpp.getText());
} else if (name.equals("leechers")) {
leechers = Integer.parseInt(xpp.getText());
} else if (name.equals("leechers_total")) {
leechersTotal = Integer.parseInt(xpp.getText());
} else if (name.equals("percentage")) {
progress = convertProgress(xpp.getText());
} else if (name.equals("num_files")) {
numFiles = Integer.parseInt(xpp.getText());
}
}
}
next = xpp.next();
if (next == XmlPullParser.START_TAG || next == XmlPullParser.END_TAG) {
name = xpp.getName();
}
}
return torrents;
} catch (XmlPullParserException e) {
throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
} catch (IOException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
}
/**
* 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) / 100;
}
/**
* Parses a KTorrent-style number string into a float value
* @param numberString A formatted number string without any letter (like GiB)
* @return The corresponding float value
*/
private static Float convertStringToFloat(String numberString) {
// KTorrent has issues with formatting its numeric values as strings. It does not always
// adhere to the localization and never indicates which formatting it will use. We therefore
// have to assume an American style format unless we have indications to assume otherwise.
int comma = numberString.indexOf(',');
int dot = numberString.indexOf('.');
if (comma > 0 && dot > 0) {
if (comma < dot) {
// Like 1,234.5
return Float.parseFloat(numberString.replace(",", ""));
} else {
// Like 1.234,5
return Float.parseFloat(numberString.replace(".", "").replace(",", "."));
}
} else {
if (comma > 0) {
// Like 234,5
return Float.parseFloat(numberString.replace(",", "."));
} else {
// Like 234.5
return Float.parseFloat(numberString);
}
}
}
/**
* Returns the size of the torrent, as parsed form some string
* @param size The size in a string format, e.g. '1,011.7 MiB'
* @return The size in number of kB
*/
public static long convertSize(String size) {
if (size.endsWith("GiB")) {
return (long)(convertStringToFloat(size.substring(0, size.length() - 4)) * 1024 * 1024 * 1024);
} else if (size.endsWith("MiB")) {
return (long)(convertStringToFloat(size.substring(0, size.length() - 4)) * 1024 * 1024);
} else if (size.endsWith("KiB")) {
return (long)(convertStringToFloat(size.substring(0, size.length() - 4)) * 1024);
} else if (size.endsWith("B")) {
return convertStringToFloat(size.substring(0, size.length() - 2)).longValue();
}
return 0;
}
/**
* Returns the rate (speed), as parsed from some string
* @param rate The rate in a string format,e.g. '9.2 KiB/s'
* @return The rate (or speed) in KiB/s
*/
public static int convertRate(String rate) {
if (rate.endsWith("MiB/s")) {
return (int) (convertStringToFloat(rate.substring(0, rate.length() - 6)) * 1024 * 1024);
} else if (rate.endsWith("KiB/s")) {
return (int) (convertStringToFloat(rate.substring(0, rate.length() - 6)) * 1024);
} else if (rate.endsWith("B/s")) {
return convertStringToFloat(rate.substring(0, rate.length() - 4)).intValue();
}
return 0;
}
/**
* Returns the status, as parsed from some string
* @param status THe torrent status in a string format, i.e. 'Leeching'
* @return The status as TorrentStatus or Unknown if it could not been parsed
*/
private static TorrentStatus convertStatus(String status) {
if (status.equals("Downloading")) {
return TorrentStatus.Downloading;
} else if (status.equals("Seeding")) {
return TorrentStatus.Seeding;
} else if (status.equals("Stopped")) {
return TorrentStatus.Paused;
} else if (status.equals("Stalled")) {
return TorrentStatus.Paused;
} else if (status.equals("Download completed")) {
return TorrentStatus.Paused;
} else if (status.equals("Not started")) {
return TorrentStatus.Paused;
} else if (status.equals("Stalled")) {
return TorrentStatus.Waiting;
} else if (status.equals("Checking data")) {
return TorrentStatus.Checking;
}
return TorrentStatus.Unknown;
}
}