class NotAvailableAnymore(Exception):
    """
    This object was available but isn't anymore.
    """ class NotifyError(Exception):
    """
    There was an error, we should notify user.
    """ class ContainerStatusWidget(SelectableText):
    def __init__(self, docker_container, nice_status=True):
        markup, attr = get_container_status_markup(docker_container, nice_status=nice_status)
        super().__init__(markup, attr) class LayerWidget(SelectableText):
    def __init__(self, ui, docker_image, index=None):
        self.ui = ui
        self.docker_image = docker_image
        label = []
        logger.debug("creating layer widget for %s", docker_image)
        if index is not None:
            separator = "└─"
            if index == 0:
                label = [separator]
            else:
                label = [2 * index * " " + separator]
        super().__init__(label + get_basic_image_markup(docker_image, with_size=True)) class View:
    # TODO: views should have API for searching and filtering
    #       there should be a map between widgets and their searchable content
    def refresh(self):
        raise NotImplementedError() class ContainerInfoGraph(urwid.BarGraph):
    def __init__(self, fg, bg, graph_bg="graph_bg", bar_width=None):
        """
        create a very simple graph

        :param fg: attr for smoothing (fg needs to be set)
        :param bg: attr for bars (bg needs to be set)
        :param graph_bg: attr for graph background
        :param bar_width: int, width of bars
        """
        # satt smoothes graph lines
        satt = {(1, 0): fg}

        super().__init__(
            [graph_bg, bg],
            hatt=[fg],
            satt=satt,
        )
        if bar_width is not None:
            # breaks badly when set too high
            self.set_bar_width(bar_width)

    def render(self, size, focus=False):
        data, top, hlines = self._get_data(size)
        maxcol, maxrow = size
        if len(data) < maxcol:
            data += [[0] for x in range(maxcol - len(data))]
            self.set_data(data, top, hlines)
            logger.debug(data)
        return super().render(size, focus)

    def rotate_value(self, val, max_val=None, adaptive_max=False):
        """

        """
        data, _, _ = self.data
        data = data[1:] + [[int(val)]]
        if adaptive_max:
            max_val = find_max(data)

        self.set_data(data, max_val)
        return max_val

    def set_max(self, value):
        data, top, hlines = self.data
        self.set_data(data, value, hlines) class WidgetBase(urwid.ListBox):
    """
    common class for widgets
    """

    def __init__(self, ui, *args, **kwargs):
        self.ui = ui
        self.search_string = None
        self.filter_query = ""
        super().__init__(*args, **kwargs)
        self.ro_content = self.body[:]  # unfiltered content of a widget
        self.body_change_lock = threading.Lock()

    def set_body(self, widgets):
        with self.body_change_lock:
            self.body[:] = widgets

    def reload_widget(self):
        # this is the easiest way to refresh body
        with self.body_change_lock:
            self.body[:] = self.body

    def _search(self, reverse_search=False):
        if self.search_string is None:
            raise NotifyError("No search pattern specified.")
        if not self.search_string:
            self.search_string = None
            return
        pos = self.focus_position
        original_position = pos
        wrapped = False
        while True:
            if reverse_search:
                obj, pos = self.body.get_prev(pos)
            else:
                obj, pos = self.body.get_next(pos)
            if obj is None:
                # wrap
                wrapped = True
                if reverse_search:
                    obj, pos = self.body[-1], len(self.body)
                else:
                    obj, pos = self.body[0], 0
            if wrapped and (
                        (pos > original_position and not reverse_search) or
                        (pos < original_position and reverse_search)
            ):
                raise NotifyError("Pattern not found: %r." % self.search_string)
            # FIXME: figure out nicer search api
            if hasattr(obj, "matches_search"):
                condition = obj.matches_search(self.search_string)
            else:
                if hasattr(obj, "original_widget"):
                    text = obj.original_widget.text
                else:
                    text = obj.text
                condition = self.search_string in text
            if condition:
                self.set_focus(pos)
                self.reload_widget()
                break

    def filter(self, s, widgets_to_filter=None):
        s = s.strip()

        if not s:
            self.filter_query = None
            self.set_body(self.ro_content)
            return

        widgets = []
        for obj in widgets_to_filter or self.ro_content:

            # FIXME: figure out nicer search api
            if hasattr(obj, "matches_search"):
                condition = obj.matches_search(s)
            else:
                if hasattr(obj, "original_widget"):
                    text = obj.original_widget.text
                else:
                    text = obj.text
                condition = s in text

            if condition:
                widgets.append(obj)
        if not widgets_to_filter:
            self.filter_query = s
        self.set_body(widgets)

    def find_previous(self, search_pattern=None):
        if search_pattern is not None:
            self.search_string = search_pattern
        self._search(reverse_search=True)

    def find_next(self, search_pattern=None):
        if search_pattern is not None:
            self.search_string = search_pattern
        self._search()

    def status_bar(self):
        columns_list = []

        def add_subwidget(markup, color_attr=None):
            if color_attr is None:
                w = urwid.AttrMap(urwid.Text(markup), "status_text")
            else:
                w = urwid.AttrMap(urwid.Text(markup), color_attr)
            columns_list.append((len(markup), w))

        if self.search_string:
            add_subwidget("Search: ")
            add_subwidget(repr(self.search_string))

        if self.search_string and self.filter_query:
            add_subwidget(", ")

        if self.filter_query:
            add_subwidget("Filter: ")
            add_subwidget(repr(self.filter_query))

        return columns_list

    @property
    def focused_docker_object(self):
        return self.get_focus()[0].docker_object class RowWidget(UnselectableRowWidget):
    def selectable(self):
        return True class UnselectableRowWidget(urwid.AttrMap):
    def __init__(self, columns, attr="main_list_dg", focus_map=MAIN_LIST_FOCUS, dividechars=1):
        self.widgets = columns
        self.columns = urwid.Columns(columns, dividechars=dividechars)
        super().__init__(self.columns, attr, focus_map=focus_map)

    @property
    def contents(self):
        return self.columns.contents

    def render(self, size, focus=False):
        for w in self.columns.widget_list:
            if hasattr(w, "set_map"):
                w.set_map('focus' if focus else 'normal')
        return urwid.AttrMap.render(self, size, focus) def assemble_rows(data, max_allowed_lengths=None, dividechars=1,
                  ignore_columns=None):
    """
    :param data: list of lists:
    [["row 1 column 1", "row 1 column 2"],
     ["row 2 column 1", "row 2 column 2"]]
    each item consists of instance of urwid.Text

    :param max_allowed_lengths: dict:
        {col_index: maximum_allowed_length}
    :param ignore_columns: list of ints, indexes which should not be calculated
    """
    rows = []
    max_lengths = {}
    ignore_columns = ignore_columns or []

    # shitty performance, here we go
    # it would be way better to do a single double loop and provide mutable variable
    # FIXME: merge this code with calculate() from above
    for row in data:
        col_index = 0
        for widget in row:
            if col_index in ignore_columns:
                continue
            l = len(widget.text)
            if max_allowed_lengths:
                if col_index in max_allowed_lengths and max_allowed_lengths[col_index] < l:
                    # l is bigger then what is allowed
                    l = max_allowed_lengths[col_index]

            max_lengths.setdefault(col_index, l)
            max_lengths[col_index] = max(l, max_lengths[col_index])
            col_index += 1

    for row in data:
        row_widgets = []
        for idx, item in enumerate(row):
            if idx in ignore_columns:
                row_widgets.append(item)
            else:
                row_widgets.append((max_lengths[idx], item))
        rows.append(
            RowWidget(row_widgets, dividechars=dividechars)
        )

    return rows class SelectableText(ColorTextMixin, AdHocAttrMap):
    def __init__(self, text, maps=None):
        maps = maps or get_map()
        super().__init__(urwid.Text(text, align="left", wrap="clip"), maps) def get_map(defult="main_list_dg"):
    return {"normal": defult, "focus": MAIN_LIST_FOCUS} class ColorText(ColorTextMixin, urwid.AttrMap):
    def __init__(self, text, color):
        super().__init__(urwid.Text(text, align="left", wrap="clip"), color) class UnselectableListBox(urwid.ListBox):
    _selectable = False def log_traceback(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logger.info("function %s is about to be started", func)
        try:
            response = func(*args, **kwargs)
        except Exception:
            log_last_traceback()
        else:
            logger.info("function %s finished", func)
            # TODO: how long it took?
            return response
    return wrapper def humanize_bytes(bytesize, precision=2):
    """
    Humanize byte size figures

    https://gist.github.com/moird/3684595
    """
    abbrevs = (
        (1 << 50, 'PB'),
        (1 << 40, 'TB'),
        (1 << 30, 'GB'),
        (1 << 20, 'MB'),
        (1 << 10, 'kB'),
        (1, 'bytes')
    )
    if bytesize == 1:
        return '1 byte'
    for factor, suffix in abbrevs:
        if bytesize >= factor:
            break
    if factor == 1:
        precision = 0
    return '%.*f %s' % (precision, bytesize / float(factor), suffix)
import pprint
import logging
import threading
import urwid
import urwidtrees
from urwid import BoxAdapter
from sen.exceptions import NotAvailableAnymore, NotifyError
from sen.tui.chunks.container import ContainerStatusWidget
from sen.tui.chunks.image import LayerWidget
from sen.tui.views.base import View
from sen.tui.widgets.graph import ContainerInfoGraph
from sen.tui.widgets.list.base import WidgetBase
from sen.tui.widgets.list.util import RowWidget, UnselectableRowWidget
from sen.tui.widgets.table import assemble_rows
from sen.tui.widgets.util import (
    SelectableText, get_map,
    ColorText, UnselectableListBox
)
from sen.util import log_traceback, humanize_bytes

        self.docker_container = docker_container

        self.stop = threading.Event()

        self.view_widgets = []

    def refresh(self):
        self.view_widgets.clear()
        self.docker_container.refresh()

        self._basic_data()
        self._net()
        self._image()
        self._process_tree()
        self._resources()
        self._labels()
        self._logs()

        # we'll update listwalker in one step: changing it periodically can be race-y
        self.set_body(self.view_widgets)
        self.set_focus(0)

    @property
    def focused_docker_object(self):
        try:
            return self.focus.columns.widget_list[0].docker_image
        except AttributeError:
            return None

    def _basic_data(self):
        data = [
            [SelectableText("Id", maps=get_map("main_list_green")),
             SelectableText(self.docker_container.container_id)],
            [SelectableText("Status", maps=get_map("main_list_green")),
             ContainerStatusWidget(self.docker_container, nice_status=False)],
            [SelectableText("Created", maps=get_map("main_list_green")),
             SelectableText("{0}, {1}".format(self.docker_container.display_formal_time_created(),
                                              self.docker_container.display_time_created()))],
            [SelectableText("Command", maps=get_map("main_list_green")),
             SelectableText(self.docker_container.command)],
        ]
        # TODO: add exit code, created, started, finished, pid
        if self.docker_container.names:
            data.append(
                [SelectableText("Name", maps=get_map("main_list_green")),
                 SelectableText("".join(self.docker_container.names))],
            )

        if self.docker_container.size_root_fs:
            data.append(
                [SelectableText("Image Size", maps=get_map("main_list_green")),
                 SelectableText(humanize_bytes(self.docker_container.size_root_fs))])
        if self.docker_container.size_rw_fs:
            data.append(
                [SelectableText("Writable Layer Size", maps=get_map("main_list_green")),
                 SelectableText(humanize_bytes(self.docker_container.size_rw_fs))])

        self.view_widgets.extend(assemble_rows(data, ignore_columns=[1]))

    def _net(self):
        try:
            net = self.docker_container.net
        except NotAvailableAnymore:
            raise NotifyError("Container %s is not available anymore" % self.docker_container)
        ports = net.ports
        data = []
        if ports:
            data.extend([[SelectableText("")], [
                SelectableText("Host Port", maps=get_map("main_list_white")),
                SelectableText("Container Port", maps=get_map("main_list_white"))
            ]])
            for container_port, host_port in ports.items():
                if host_port and container_port:
                    data.append([
                        SelectableText(host_port), SelectableText(container_port)
                    ])

        ips = net.ips
        logger.debug(ips)
        if ips:
            data.extend([[SelectableText("")], [
                SelectableText("Network Name", maps=get_map("main_list_white")),
                SelectableText("IP Address", maps=get_map("main_list_white"))
            ]])
            for net_name, net_data in ips.items():
                a4 = net_data.get("ip_address4", "none")
                a6 = net_data.get("ip_address6", "")
                data.append([
                    SelectableText(net_name), SelectableText(a4)
                ])
                if a6:
                    data.append([
                        SelectableText(net_name), SelectableText(a6)
                    ])
        if data:
            self.view_widgets.extend(assemble_rows(data, dividechars=3, ignore_columns=[1]))

    def _image(self):
        if self.docker_container.image:
            self.view_widgets.append(RowWidget([SelectableText("")]))
            self.view_widgets.append(RowWidget([SelectableText("Image", maps=get_map("main_list_white"))]))
            self.view_widgets.append(RowWidget([LayerWidget(self.ui, self.docker_container.image)]))

    def _resources(self):
        self.view_widgets.append(RowWidget([SelectableText("")]))
        self.view_widgets.append(RowWidget([SelectableText("Resource Usage",
                                                     maps=get_map("main_list_white"))]))
        cpu_g = ContainerInfoGraph("graph_lines_cpu_tips", "graph_lines_cpu")
        mem_g = ContainerInfoGraph("graph_lines_mem_tips", "graph_lines_mem")
        blk_r_g = ContainerInfoGraph("graph_lines_blkio_r_tips", "graph_lines_blkio_r")
        blk_w_g = ContainerInfoGraph("graph_lines_blkio_w_tips", "graph_lines_blkio_w")
        net_r_g = ContainerInfoGraph("graph_lines_net_r_tips", "graph_lines_net_r")
        net_w_g = ContainerInfoGraph("graph_lines_net_w_tips", "graph_lines_net_w")

        cpu_label = ColorText("CPU ", "graph_lines_cpu_legend")
        cpu_value = ColorText("0.0 %", "graph_lines_cpu_legend")
        mem_label = ColorText("Memory ", "graph_lines_mem_legend")
        mem_value = ColorText("0.0 %", "graph_lines_mem_legend")
        blk_r_label = ColorText("I/O Read ", "graph_lines_blkio_r_legend")

        blk_r_value = ColorText("0 B", "graph_lines_blkio_r_legend")