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)
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) ]
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()
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"""
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)
def clear(self): immutable = dict((key, val) for (key, val) in self.items() if key in self.IMMUTABLE) Bunch.clear(self) self.update(immutable)
def __getattr__(self, name): try: return Bunch.__getattr__(self, name) except AttributeError: return self[name]
def __init__(self, proxy, values=None): dict.__setattr__(self, "proxy", proxy) Bunch.__init__(self, values)
.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)
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()
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")