예제 #1
0
    def __call__(self, environ, start_response):
        """ Invoke the Controller.
        """
        c._debug = []
        c._timezone = config['pyroscope.timezone']

        c.engine = Bunch()
        c.engine.startup = fmt.human_duration(rtorrent.get_startup(), precision=3)

        #XXX Use multimethod, or get from poller
        for attr, method in self.GLOBAL_STATE.items():
            c.engine[attr] = getattr(self.proxy.rpc, method)()

        return BaseController.__call__(self, environ, start_response)
예제 #2
0
    def _get_messages(self, torrents):
        """ Get messages from a list of torrents.
        """
        if asbool(request.params.get("_debug")) and torrents:
            torrents[0].message += " [FAKE MESSAGE FOR TESTING]"

        return [
            Bunch(hash=item.hash,
                  name=item.name,
                  text=item.message,
                  tooltip=make_tooltip(item),
                  domains=", ".join(sorted(item.tracker_domains)))
            for item in torrents if item.is_open and item.message and not any(
                ignore in item.message for ignore in self.IGNORE_MESSAGES)
        ]
예제 #3
0
class StatsController(PageController):

    VIEWS = (Bunch(action="trackers",
                   icon="tracker.12 Torrent Stats per Tracker",
                   title="Trackers"), )

    def __init__(self):
        self.proxy = rtorrent.Proxy()
        self.views = dict((view.action, view) for view in self.VIEWS)

    def __before__(self):
        # Set list of views
        c.views = self.VIEWS

    def _render(self):
        return render("/pages/stats.mako")

    def trackers(self):
        # XXX: do this in the __before__, need to find out the action name though
        c.view = self.views['trackers']

        # Get list of torrents
        torrents = list(rtorrent.View(self.proxy, "main").items())

        #c.domains = set(domain for item in torrents for domain in item.tracker_domains)
        #c.active_up = [item for item in torrents if item.up_rate and not item.down_rate]
        #c.active_down = [item for item in torrents if not item.up_rate and item.down_rate]
        #c.active_both = [item for item in torrents if item.up_rate and item.down_rate]
        #c.ratios = [item.ratio for item in torrents if item.down_total or item.up_total]
        #c.seeds = [item for item in torrents if not item.down_total and item.up_total]

        #c.counts = {}
        #for attr in ("is_open", "complete"):
        #    c.counts[attr] = sum(getattr(item, attr) for item in torrents)

        # Sum up different values per tracker, over all torrents
        c.trackers, c.totals = get_tracker_stats(torrents)

        return self._render()

    def index(self):
        # Redirect to list of active torrents
        ##return self._render()
        ##return redirect_to(action="trackers")
        return self.trackers()
예제 #4
0
    def data(self, id):
        if id == "timeline.xml":
            response.headers['Content-Type'] = 'application/xml; charset="utf-8"'

            proxy = rtorrent.Proxy.create()
            torrents = list(rtorrent.View(proxy, 'main').items())
            torrent_data = []
            rtorrent_start = rtorrent.get_startup()

            span_data = defaultdict(list)
            for item in torrents:
                title = shorten(obfuscate(item.name))
                if item.is_open:
                    # Store in minute-sized buckets
                    span_data[item.state_changed // self.BUCKET_SIZE * self.BUCKET_SIZE].append(item)
                elif item.message:
                    # Add closed torrents with a message (like unregistered ones)
                    torrent_data.append(u'<event start="%s" title="Stopped %s">'
                            u'Stopped %s, possibly due to %s @ %s</event>' % (
                        time.strftime("%c", time.localtime(item.state_changed)),
                        escape(quoted(title), quote=True),
                        escape(quoted(obfuscate(item.name))),
                        escape(quoted(item.message)),
                        ", ".join(escape(obfuscate(i)) for i in item.tracker_domains),
                    ))

                tied_file = os.path.expanduser(item.tied_to_file)
                if os.path.exists(tied_file):
                    torrent_data.append(u'<event start="%s" title="Downloaded %s">'
                            u'Downloaded metafile for %s</event>' % (
                        time.strftime("%c", time.localtime(
                            os.path.getmtime(tied_file)
                        )),
                        escape(quoted(title), quote=True),
                        escape(quoted(obfuscate(item.name))),
                    ))

            for bucket, items in span_data.items():
                if len(items) > self.HOTSPOT_SIZE:
                    # hot spot, f.x. happens when you restart rTorrent
                    # since we filtered open torrents only, they had to be started at that point
                    entries = [Bunch(
                        title = u"Started %d torrents within %d secs, seeding them..." % (
                            len(items), self.BUCKET_SIZE),
                        start = bucket,
                        text = ",\n".join(shorten(obfuscate(item.name), 20) for item in items[:40])
                             + (", ..." if len(items) > 40 else ""),
                    )]
                else:
                    # torrent gets its own event
                    entries = [Bunch(
                            title = u"%s %s" % (
                                u"Seeding" if item.complete else u"Leeching",
                                quoted(shorten(obfuscate(item.name))) ),
                            start = item.state_changed,
                            text = u"NAME: %s | %s" % (
                                obfuscate(item.name), make_tooltip(item)),
                        ) for item in items
                    ]

                torrent_data.extend([u'<event start="%s" end="%s" title="%s">%s</event>' % (
                        time.strftime(u"%c", time.localtime(entry.start)),
                        c.now,
                        escape(entry.title, quote=True),
                        escape(entry.text),
                    ) for entry in entries
                ])

            if rtorrent_start:
                torrent_data.append(u'<event start="%s" title="rTorrent started"></event>' % (
                    time.strftime(u"%c", time.localtime(rtorrent_start)),
                ))

            torrent_data.append(u'<event start="%s" title="The time is now %s"></event>' % (
                c.now, time.strftime("%Y-%m-%d %H:%M:%S", self.now),
            ))

            torrent_data = u'\n'.join(torrent_data)

            return u"""<?xml version="1.0" encoding="utf-8"?>
<data>""" + torrent_data + u"""
예제 #5
0
    def jit(self):
        ##import copy
        from math import log
        from pyroscope.controllers.stats import domain_key, get_tracker_stats

        # Get rTorrent proxy and a torrents list
        proxy = rtorrent.Proxy()
        torrents = list(rtorrent.View(proxy, 'main').items())
        tracker_stats, totals = get_tracker_stats(torrents)

        # Create root node
        root = Bunch(
            id = "root",
            name = proxy.id,
            data = {"$area": 100},
            children = [],  
        )
        
        # Add tracker hierarchy
        trackers = set(domain_key(item) for item in torrents)
        trackers_node = dict(
            id = "trackers",
            name = "Data Size per Tracker",
            data = {"$area": 50},
            children = [{
                    "id": "trk-%s" % tracker.replace('*', '_').replace('.', '-')
                        .replace(',', '').replace(' ', ''),  
                    "name": "%s [%d / %s / %.1f%%]" % (
                        tracker, tracker_stats[tracker]["loaded"],
                        bibyte(tracker_stats[tracker]["size"]),
                        100.0 * tracker_stats[tracker]["size"] / totals["size"],
                    ),
                    "data": {
                        ##"$area": tracker_stats[tracker]["loaded"] or 1,
                        "$area": tracker_stats[tracker]["size"],
                        "$color": int(100 * log(max(1, 100 * tracker_stats[tracker]["size"] // totals["size"])) / log(100)),
                    },  
                    "children": [],    
                }
                for tracker in sorted(trackers)
            ],  
        )
        root.children.append(trackers_node)

        def copy_children(scope, children):
            for item in children:
                item = item.copy()
                item["id"] = "%s-%s" % (scope, item["id"])
                yield item

        # Status selection
        status_node = dict(
            id = "status",
            name = "Status",
            data = {"$area": 50},
            children = [{
                    "id": "status-%s" % status,  
                    "name": "%s [%d]" % (status, totals[status]),
                    "data": {"$area": totals[status] or 1},  
                    "children": [], ##list(copy_children(status, trackers_node["children"])),
                }
                for status in ('active', 'done', 'incomplete', 'open', 'closed', 'prv', 'pub')
            ],  
        )
        ##root.children.append(status_node)

        # Recurse over tree and add areas
        def area_sum(tree):
            area = 0
            for node in tree["children"]:
                if "$area" not in node["data"]:
                    area_sum(node)
                area += node["data"]["$area"]

            tree["data"]["$area"] = area
            return tree
        
        # Return graph data
        return area_sum(root)
예제 #6
0
 def clear(self):
     immutable = dict((key, val) for (key, val) in self.items() if key in self.IMMUTABLE)
     Bunch.clear(self)
     self.update(immutable)
예제 #7
0
 def __getattr__(self, name):
     try:
         return Bunch.__getattr__(self, name)
     except AttributeError:
         return self[name]
예제 #8
0
 def __init__(self, proxy, values=None):
     dict.__setattr__(self, "proxy", proxy)
     Bunch.__init__(self, values)
예제 #9
0
                        .replace(".update.",".*.")
                        .replace(".announce.",".*.")
                        .replace(".www.",".*.")
                        .lstrip("1234567890.")
                    for url in self.tracker_urls)
                value.discard('')
            else:
                accessor = ("" if name.startswith("is_") else "get_") + name
                try:
                    value = getattr(self.proxy.rpc.d, accessor)(self["hash"])
                except Error, exc:
                    raise TorrentAttributeError("Attribute %r not available (%s)" % (name, exc))
            self[name] = value # cache it for later access
            return value
        else:
            return Bunch.__getitem__(self, name)


    def __getattr__(self, name):
        try:
            return Bunch.__getattr__(self, name)
        except AttributeError:
            return self[name]


    def clear(self):
        immutable = dict((key, val) for (key, val) in self.items() if key in self.IMMUTABLE)
        Bunch.clear(self)
        self.update(immutable)

예제 #10
0
class AdminController(PageController):

    VIEWS = (
        Bunch(action="config",
              icon="cog.12 View Configuration Files",
              title="Show Config"),
        Bunch(action="log", icon="file.12 View Log Files", title="Show Logs"),
    )

    def __init__(self):
        self.proxy = rtorrent.Proxy.create()
        self.views = dict((view.action, view) for view in self.VIEWS)

    def __before__(self):
        # Set list of views
        c.views = self.VIEWS

    def _render(self):
        return render("/pages/admin.mako")

    def config(self):
        c.view = self.views['config']

        # Build search path
        session_dir = self.proxy.rpc.get_session()
        cur_dir = self.proxy.rpc.system.get_cwd()
        search_path = []
        for path in (session_dir, os.path.dirname(session_dir.rstrip(os.sep)),
                     cur_dir, "~"):
            path = os.path.expanduser(path.rstrip(os.sep))
            if path not in search_path:
                search_path.append(path)

        # Find rtorrent.rc file
        for path, name in product(search_path,
                                  ("rtorrent.rc", ".rtorrent.rc")):
            c.filename = os.path.join(path, name)
            if os.path.exists(c.filename):
                break
        else:
            c.filename = None
            c._messages.append(
                "Cannot find rTorrent configuration file in %s!" %
                ', '.join(search_path))

        # Load file
        if c.filename:
            with closing(open(c.filename, "r")) as handle:
                c.lines = handle.readlines()

        return self._render()

    def log(self):
        c.view = self.views['log']
        c.filename = "N/A"
        c.lines = []

        return self._render()

    def index(self):
        # Redirect to config file view
        ##return self._render()
        ##return redirect_to(action="config")
        return self.config()
예제 #11
0
class ViewController(PageController):

    IGNORE_MESSAGES = [
        "Timeout was reached",
        "Tried all trackers",
        "Could not parse bencoded data",
        "Couldn't connect to server",
    ]
    VIEWS = (
        Bunch(action="active", title="Active", icon="nuked.12", stock=False),
        Bunch(action="incomplete", title="Incomplete", icon="box-cross.12"),
        Bunch(action="stopped", title="Stopped", icon="stopped.12"),
        Bunch(action="hashing", title="Hashing", icon="hash.12"),
        Bunch(action="name", title="Loaded", icon="torrent.12"),
        #Bunch(action="main", title="Loaded"),
        Bunch(action="complete", title="Completed", icon="box-check.12"),
        Bunch(action="seeding", title="Seeding", icon="green_up_single.12"),
        Bunch(action="started", title="Started", icon="started.12"),
    )

    def __init__(self):
        self.proxy = rtorrent.Proxy()
        self.views = dict((view.action, view) for view in self.VIEWS)

    def __before__(self):
        # Set list of views
        c.views = self.VIEWS

        # Set flags
        c.refresh_rate = request.params.get("refresh", 60)
        c.obfuscate = asbool(
            request.params.get("_obfuscate",
                               request.params.get("_obfuscated")))
        c.filter_mode = request.params.get("filter_mode", "AND")

    def _get_messages(self, torrents):
        """ Get messages from a list of torrents.
        """
        if asbool(request.params.get("_debug")) and torrents:
            torrents[0].message += " [FAKE MESSAGE FOR TESTING]"

        return [
            Bunch(hash=item.hash,
                  name=item.name,
                  text=item.message,
                  tooltip=make_tooltip(item),
                  domains=", ".join(sorted(item.tracker_domains)))
            for item in torrents if item.is_open and item.message and not any(
                ignore in item.message for ignore in self.IGNORE_MESSAGES)
        ]

    def _model_fixup(self):
        """ Go through c.torrents and fix up the items; also do some stats.
        """
        c.up_total, c.down_total = 0, 0
        for item in c.torrents:
            c.up_total += item.up_rate
            c.down_total += item.down_rate

            ##d.get_peers_accounted()
            ##d.get_peers_connected()
            ##d.get_peers_max()
            ##d.get_peers_min()
            ##d.get_peers_not_connected()
            #XXX This seems reliable only for active torrents
            item.seeders = item.peers_complete + (item.is_open
                                                  and item.complete)
            item.leeches = (item.peers_not_connected + item.peers_connected -
                            item.peers_complete +
                            (item.is_open and not item.complete))

            item.tooltip = make_tooltip(item)
            item.ratio_not0 = item.ratio / 1000.0 or 1E-12
            item.domains = ", ".join(item.tracker_domains)

            # XXX Fix bug in XMLRPC (using 32bit integers?)
            if item.size_bytes < 0:
                item.size_bytes = (
                    item.size_chunks -
                    1) * item.chunk_size + item.size_bytes % item.chunk_size
            if item.down_total < 0 and item.up_total > 0:
                item.down_total = int(item.up_total / item.ratio_not0)
            elif item.down_total > 0 and item.up_total < 0:
                item.up_total = int(item.down_total * item.ratio_not0)

        c.messages = self._get_messages(c.torrents)
        """ XXX stats code currently not used!
        
        domains = set(domain for item in all_torrents for domain in item.tracker_domains)
        active_up = [item for item in c.torrents if item.up_rate and not item.down_rate]
        active_down = [item for item in c.torrents if not item.up_rate and item.down_rate]
        active_both = [item for item in c.torrents if item.up_rate and item.down_rate]
        ratios = [item.ratio for item in all_torrents if item.down_total or item.up_total]
        #queued = [item for item in all_torrents if not (item.down_total or item.up_total)]
        seeds = [item for item in all_torrents if not item.down_total and item.up_total]

        counts = {}
        for attr in ("is_open", "complete"):
            counts[attr] = sum(getattr(item, attr) for item in all_torrents)
        """

    def _get_active(self):
        """ Get active torrents.
        """
        # Filter & decorate, sort, undecorate
        return [
            i for _, _, i in sorted([(
                item.up_rate, item.down_rate,
                item) for item in rtorrent.View(self.proxy, "main").items()
                                     if item.down_rate or item.up_rate],
                                    reverse=True)
        ]

    def _filter(self, torrents, query):
        """ Filter list of torrents.
        """
        def fields(item):
            "Which fields to search in..."
            yield item.name
            for i in item.tracker_domains:
                yield i.lstrip("*.")
            for i in _make_state(item):
                yield i

        if query:
            patterns = [
                "*%s*" % p if '*' not in p else p
                for p in query.lower().split()
            ]

            # AND: all patterns must be matched
            # OR:  at least 1 pattern must be matched
            threshold = len(patterns) if c.filter_mode == "AND" else 1

            return [
                item for item in torrents if threshold <= sum(
                    any(fnmatch(i.lower(), p) for i in fields(item))
                    for p in patterns)
            ]
        else:
            return torrents

    def _normalized_filter(self):
        """ Normalize filter query.
        """
        filter = request.params.get("filter", "").lower()
        if filter == "filter...":
            filter = ""
        return ' '.join([
            "%s*" % p if '*' not in p and '[' not in p else p
            for p in filter.split()
        ])

    def list(self, id):
        c.view = self.views[id]

        # Get list of torrents
        if c.view.get("stock", True):
            # Built-in view
            c.torrents = list(rtorrent.View(self.proxy, id).items())
        else:
            # Handle non-stock views
            c.torrents = getattr(self, "_get_" + id)()

        # Handle filter
        c.torrents_unfiltered = len(c.torrents)
        c.filter = self._normalized_filter()
        c.torrents = self._filter(c.torrents, c.filter)

        # Build view model
        self._model_fixup()

        # Return a rendered template
        return render("pages/view.mako")

    def index(self):
        # Redirect to list of active torrents
        ##return redirect_to(action="list", id="active")
        return self.list("active")