package org.transdroid.daemon.Tfb4rt; 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 Torrentflux-b4rt-specific parser for it's stats.xml output. * * @author erickok * */ public class StatsParser { public static List<Torrent> parse(Reader in) throws DaemonException { try { // Use a PullParser to handle XML tags one by one XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser(); //in = new FileReader("/sdcard/tfdebug.xml"); xpp.setInput(in); // Temp variables to load into torrent objects int id = 0; String tname = ""; int time = 0; // Seconds remaining int up = 0; // Upload rate in seconds int down = 0; // Download rate in seconds float progress = 0; // Part [0,1] completed TorrentStatus status = TorrentStatus.Unknown; long size = 0; // Total size long upSize = -1; // Total uploaded // Start pulling List<Torrent> torrents = new ArrayList<Torrent>(); int next = xpp.nextTag(); String name = xpp.getName(); if (name.equals("html")) { // We are given an html page instead of xml data; probably an authentication error throw new DaemonException(DaemonException.ExceptionType.AuthenticationFailure, "HTML tag found instead of XML data; authentication error?"); } if (name.equals("rss")) { // We are given an html page instead of xml data; probably an authentication error throw new DaemonException(DaemonException.ExceptionType.UnexpectedResponse, "RSS feed found instead of XML data; configuration error?"); } while (next != XmlPullParser.END_DOCUMENT) { if (next == XmlPullParser.END_TAG && name.equals("transfer")) { // End of a 'transfer' item, add gathered torrent data torrents.add(new Torrent( id++, tname, tname, status, null, down, up, 0, 0, 0, 0, time, (progress > 1L? size: (long)(progress * size)), // Cap the download size to the torrent size (upSize == -1? (progress > 1L? (long)(progress * size): 0L): upSize), // If T. Up doesn't exist, we can use the progress size instead size, (status == TorrentStatus.Seeding? 1F: progress), 0f, null, // Not supported in the XML stats null, null, null, Daemon.Tfb4rt)); } else if (next == XmlPullParser.START_TAG && name.equals("transfer")){ // Start of a new 'transfer' item, for which the name is in the first attribute // i.e. '<transfer name="_isoHunt_ubuntu-9.10-desktop-amd64.iso.torrent">' tname = xpp.getAttributeValue(0); // Reset gathered torrent data size = 0; status = TorrentStatus.Unknown; progress = 0; down = 0; up = 0; time = 0; } else if (next == XmlPullParser.START_TAG && name.equals("transferStat")){ // Encountered an actual stat, which will always have an attribute name indicating it's type // i.e. '<transferStat name="Size">691 MB</transferStat>' String type = xpp.getAttributeValue(0); next = xpp.next(); if (next == XmlPullParser.TEXT) { try { if (type.equals("Size")) { size = convertSize(xpp.getText()); } else if (type.equals("Status")) { status = convertStatus(xpp.getText()); } else if (type.equals("Progress")) { progress = convertProgress(xpp.getText()); } else if (type.equals("Down")) { down = convertRate(xpp.getText()); } else if (type.equals("Up")) { up = convertRate(xpp.getText()); } else if (type.equals("Estimated Time")) { time = convertEta(xpp.getText()); } else if (type.equals("T. Up")) { upSize = convertSize(xpp.getText()); } } catch (Exception e) { throw new DaemonException(ExceptionType.ConnectionError, e.toString()); } } } 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%' * @return The part done as [0..1] fraction */ private static float convertProgress(String progress) { if (progress.endsWith("%")) { return Float.parseFloat(progress.substring(0, progress.length() - 1).replace(",", "")) / 100; } return 0; } /** * 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 number of kB */ private static long convertSize(String size) { if (size.endsWith("GB")) { return (long)(Float.parseFloat(size.substring(0, size.length() - 3)) * 1024 * 1024 * 1024); } else if (size.endsWith("MB")) { return (long)(Float.parseFloat(size.substring(0, size.length() - 3)) * 1024 * 1024); } else if (size.endsWith("kB")) { return (long)(Float.parseFloat(size.substring(0, size.length() - 3)) * 1024); } else if (size.endsWith("B")) { return (long)(Float.parseFloat(size.substring(0, size.length() - 2))); } return 0; } /** * Returns the eta (estimated time of arrival), as parsed from some string * @param time The time in a string format, i.e. '1d 06:20:48' or '21:36:49' * @return The eta in number of seconds */ private static int convertEta(String time) { if (!time.contains(":")) { // Not running (something like 'Torrent Stopped' is shown) return -1; } int seconds = 0; // Days if (time.contains("d ")) { seconds += Integer.parseInt(time.substring(0, time.indexOf("d "))) * 60 * 60 * 24; time = time.substring(time.indexOf("d ") + 2); } // Hours, minutes and seconds String[] parts = time.split(":"); if (parts.length > 2) { seconds += Integer.parseInt(parts[0]) * 60 * 60; seconds += Integer.parseInt(parts[1]) * 60; seconds += Integer.parseInt(parts[2]); } else if (parts.length > 1) { seconds += Integer.parseInt(parts[0]) * 60; seconds += Integer.parseInt(parts[1]); } else { seconds += Integer.parseInt(time); } return seconds; } /** * Returns the rate (speed), as parsed from some string * @param rate The rate in a string format, i.e. '9 kB/s' * @return The rate (or speed) in kB/s */ private static int convertRate(String rate) { if (rate.endsWith("MB/s")) { return (int) (Float.parseFloat(rate.substring(0, rate.length() - 5)) * 1024 * 1024); } else if (rate.endsWith("kB/s")) { return (int) (Float.parseFloat(rate.substring(0, rate.length() - 5)) * 1024); } else if (rate.endsWith("B/s")) { return (int) Float.parseFloat(rate.substring(0, rate.length() - 4)); } 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("Leeching")) { return TorrentStatus.Downloading; } else if (status.equals("Seeding")) { return TorrentStatus.Seeding; } else if (status.equals("Stopped")) { return TorrentStatus.Paused; } else if (status.equals("New")) { return TorrentStatus.Paused; } else if (status.equals("Done")) { return TorrentStatus.Paused; } return TorrentStatus.Unknown; } }