summaryrefslogtreecommitdiffstats
path: root/pym/_emerge/JobStatusDisplay.py
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2009-06-22 18:21:56 +0000
committerZac Medico <zmedico@gentoo.org>2009-06-22 18:21:56 +0000
commit69500d28402817fb70c104501b5c8f4f54650f4a (patch)
treeb718c8028eb09ad8e8fb5e1503df58abb502b712 /pym/_emerge/JobStatusDisplay.py
parent13d9a8d5f95575c814570cd4e5713541ed022f37 (diff)
downloadportage-69500d28402817fb70c104501b5c8f4f54650f4a.tar.gz
portage-69500d28402817fb70c104501b5c8f4f54650f4a.tar.bz2
portage-69500d28402817fb70c104501b5c8f4f54650f4a.zip
Bug #275047 - Split _emerge/__init__.py into smaller pieces (part 2).
Thanks to Sebastian Mingramm (few) <s.mingramm@gmx.de> for this patch. svn path=/main/trunk/; revision=13667
Diffstat (limited to 'pym/_emerge/JobStatusDisplay.py')
-rw-r--r--pym/_emerge/JobStatusDisplay.py267
1 files changed, 267 insertions, 0 deletions
diff --git a/pym/_emerge/JobStatusDisplay.py b/pym/_emerge/JobStatusDisplay.py
new file mode 100644
index 000000000..7b111da05
--- /dev/null
+++ b/pym/_emerge/JobStatusDisplay.py
@@ -0,0 +1,267 @@
+import formatter
+import os
+import sys
+import time
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+try:
+ import portage
+except ImportError:
+ from os import path as osp
+ sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
+ import portage
+
+from portage.output import xtermTitle
+
+from _emerge.getloadavg import getloadavg
+
+class JobStatusDisplay(object):
+
+ _bound_properties = ("curval", "failed", "running")
+ _jobs_column_width = 48
+
+ # Don't update the display unless at least this much
+ # time has passed, in units of seconds.
+ _min_display_latency = 2
+
+ _default_term_codes = {
+ 'cr' : '\r',
+ 'el' : '\x1b[K',
+ 'nel' : '\n',
+ }
+
+ _termcap_name_map = {
+ 'carriage_return' : 'cr',
+ 'clr_eol' : 'el',
+ 'newline' : 'nel',
+ }
+
+ def __init__(self, out=sys.stdout, quiet=False, xterm_titles=True):
+ object.__setattr__(self, "out", out)
+ object.__setattr__(self, "quiet", quiet)
+ object.__setattr__(self, "xterm_titles", xterm_titles)
+ object.__setattr__(self, "maxval", 0)
+ object.__setattr__(self, "merges", 0)
+ object.__setattr__(self, "_changed", False)
+ object.__setattr__(self, "_displayed", False)
+ object.__setattr__(self, "_last_display_time", 0)
+ object.__setattr__(self, "width", 80)
+ self.reset()
+
+ isatty = hasattr(out, "isatty") and out.isatty()
+ object.__setattr__(self, "_isatty", isatty)
+ if not isatty or not self._init_term():
+ term_codes = {}
+ for k, capname in self._termcap_name_map.iteritems():
+ term_codes[k] = self._default_term_codes[capname]
+ object.__setattr__(self, "_term_codes", term_codes)
+ encoding = sys.getdefaultencoding()
+ for k, v in self._term_codes.items():
+ if not isinstance(v, basestring):
+ self._term_codes[k] = v.decode(encoding, 'replace')
+
+ def _init_term(self):
+ """
+ Initialize term control codes.
+ @rtype: bool
+ @returns: True if term codes were successfully initialized,
+ False otherwise.
+ """
+
+ term_type = os.environ.get("TERM", "vt100")
+ tigetstr = None
+
+ try:
+ import curses
+ try:
+ curses.setupterm(term_type, self.out.fileno())
+ tigetstr = curses.tigetstr
+ except curses.error:
+ pass
+ except ImportError:
+ pass
+
+ if tigetstr is None:
+ return False
+
+ term_codes = {}
+ for k, capname in self._termcap_name_map.iteritems():
+ code = tigetstr(capname)
+ if code is None:
+ code = self._default_term_codes[capname]
+ term_codes[k] = code
+ object.__setattr__(self, "_term_codes", term_codes)
+ return True
+
+ def _format_msg(self, msg):
+ return ">>> %s" % msg
+
+ def _erase(self):
+ self.out.write(
+ self._term_codes['carriage_return'] + \
+ self._term_codes['clr_eol'])
+ self.out.flush()
+ self._displayed = False
+
+ def _display(self, line):
+ self.out.write(line)
+ self.out.flush()
+ self._displayed = True
+
+ def _update(self, msg):
+
+ out = self.out
+ if not self._isatty:
+ out.write(self._format_msg(msg) + self._term_codes['newline'])
+ self.out.flush()
+ self._displayed = True
+ return
+
+ if self._displayed:
+ self._erase()
+
+ self._display(self._format_msg(msg))
+
+ def displayMessage(self, msg):
+
+ was_displayed = self._displayed
+
+ if self._isatty and self._displayed:
+ self._erase()
+
+ self.out.write(self._format_msg(msg) + self._term_codes['newline'])
+ self.out.flush()
+ self._displayed = False
+
+ if was_displayed:
+ self._changed = True
+ self.display()
+
+ def reset(self):
+ self.maxval = 0
+ self.merges = 0
+ for name in self._bound_properties:
+ object.__setattr__(self, name, 0)
+
+ if self._displayed:
+ self.out.write(self._term_codes['newline'])
+ self.out.flush()
+ self._displayed = False
+
+ def __setattr__(self, name, value):
+ old_value = getattr(self, name)
+ if value == old_value:
+ return
+ object.__setattr__(self, name, value)
+ if name in self._bound_properties:
+ self._property_change(name, old_value, value)
+
+ def _property_change(self, name, old_value, new_value):
+ self._changed = True
+ self.display()
+
+ def _load_avg_str(self):
+ try:
+ avg = getloadavg()
+ except OSError:
+ return 'unknown'
+
+ max_avg = max(avg)
+
+ if max_avg < 10:
+ digits = 2
+ elif max_avg < 100:
+ digits = 1
+ else:
+ digits = 0
+
+ return ", ".join(("%%.%df" % digits ) % x for x in avg)
+
+ def display(self):
+ """
+ Display status on stdout, but only if something has
+ changed since the last call.
+ """
+
+ if self.quiet:
+ return
+
+ current_time = time.time()
+ time_delta = current_time - self._last_display_time
+ if self._displayed and \
+ not self._changed:
+ if not self._isatty:
+ return
+ if time_delta < self._min_display_latency:
+ return
+
+ self._last_display_time = current_time
+ self._changed = False
+ self._display_status()
+
+ def _display_status(self):
+ # Don't use len(self._completed_tasks) here since that also
+ # can include uninstall tasks.
+ curval_str = str(self.curval)
+ maxval_str = str(self.maxval)
+ running_str = str(self.running)
+ failed_str = str(self.failed)
+ load_avg_str = self._load_avg_str()
+
+ color_output = StringIO()
+ plain_output = StringIO()
+ style_file = portage.output.ConsoleStyleFile(color_output)
+ style_file.write_listener = plain_output
+ style_writer = portage.output.StyleWriter(file=style_file, maxcol=9999)
+ style_writer.style_listener = style_file.new_styles
+ f = formatter.AbstractFormatter(style_writer)
+
+ number_style = "INFORM"
+ f.add_literal_data("Jobs: ")
+ f.push_style(number_style)
+ f.add_literal_data(curval_str)
+ f.pop_style()
+ f.add_literal_data(" of ")
+ f.push_style(number_style)
+ f.add_literal_data(maxval_str)
+ f.pop_style()
+ f.add_literal_data(" complete")
+
+ if self.running:
+ f.add_literal_data(", ")
+ f.push_style(number_style)
+ f.add_literal_data(running_str)
+ f.pop_style()
+ f.add_literal_data(" running")
+
+ if self.failed:
+ f.add_literal_data(", ")
+ f.push_style(number_style)
+ f.add_literal_data(failed_str)
+ f.pop_style()
+ f.add_literal_data(" failed")
+
+ padding = self._jobs_column_width - len(plain_output.getvalue())
+ if padding > 0:
+ f.add_literal_data(padding * " ")
+
+ f.add_literal_data("Load avg: ")
+ f.add_literal_data(load_avg_str)
+
+ # Truncate to fit width, to avoid making the terminal scroll if the
+ # line overflows (happens when the load average is large).
+ plain_output = plain_output.getvalue()
+ if self._isatty and len(plain_output) > self.width:
+ # Use plain_output here since it's easier to truncate
+ # properly than the color output which contains console
+ # color codes.
+ self._update(plain_output[:self.width])
+ else:
+ self._update(color_output.getvalue())
+
+ if self.xterm_titles:
+ xtermTitle(" ".join(plain_output.split()))