# -*- coding: utf-8 -*-
"""
Display network transfer rate.

Configuration parameters:
    all_interfaces: ignore self.interfaces, but not self.interfaces_blacklist
        (default True)
    cache_timeout: how often we refresh this module in seconds
        (default 2)
    devfile: location of dev file under /proc
        (default '/proc/net/dev')
    format: format of the module output
        (default '{interface}: {total}')
    format_no_connection: when there is no data transmitted from the start of the plugin
        (default '')
    format_value: format to use for values
        (default "[\?min_length=11 {value:.1f} {unit}]")
    hide_if_zero: hide indicator if rate == 0
        (default False)
    interfaces: comma separated list of interfaces to track
        (default [])
    interfaces_blacklist: comma separated list of interfaces to ignore
        (default 'lo')
    si_units: use SI units
        (default False)
    sum_values: sum values of each interface instead of taking the top one
        (default False)
    thresholds: thresholds to use for colors
        (default [(0, 'bad'), (1024, 'degraded'), (1024 * 1024, 'good')])
    unit: unit to use. If the unit contains a multiplier prefix, only this
        exact unit will ever be used
        (default "B/s")

Format placeholders:
    {down} download rate
    {interface} name of interface
    {total} total rate
    {up} upload rate

format_value placeholders:
    {unit} current unit
    {value} numeric value

Color thresholds:
    {down} Change color based on the value of down
    {total} Change color based on the value of total
    {up} Change color based on the value of up

@author shadowprince
@license Eclipse Public License

SAMPLE OUTPUT
{'full_text': 'eno1:  852.2 KiB/s'}
"""

from __future__ import division  # python2 compatibility
from time import time


class Py3status:
    """
    """
    # available configuration parameters
    all_interfaces = True
    cache_timeout = 2
    devfile = '/proc/net/dev'
    format = "{interface}: {total}"
    format_no_connection = ''
    format_value = "[\?min_length=11 {value:.1f} {unit}]"
    hide_if_zero = False
    interfaces = []
    interfaces_blacklist = 'lo'
    si_units = False
    sum_values = False
    thresholds = [(0, "bad"), (1024, "degraded"), (1024 * 1024, "good")]
    unit = "B/s"

    class Meta:

        def deprecate_function(config):
            # support old thresholds
            precision = config.get('precision', 1)
            padding = 3 + 1 + precision + 1 + 5
            format_value = "[\?min_length={padding} {{value:.{precision}f}} {{unit}}]".format(
                padding=padding, precision=precision
            )
            return {'format_value': format_value}

        deprecated = {
            'function': [
                {'function': deprecate_function},
            ],
            'remove': [
                {
                    'param': 'precision',
                    'msg': 'obsolete, use format_value instead',
                },
            ]
        }

    def post_config_hook(self):
        # parse some configuration parameters
        if not isinstance(self.interfaces, list):
            self.interfaces = self.interfaces.split(',')
        if not isinstance(self.interfaces_blacklist, list):
            self.interfaces_blacklist = self.interfaces_blacklist.split(',')
        placeholders = self.py3.get_placeholder_formats_list(self.format_value)
        values = ['{%s}' % x[1] for x in placeholders if x[0] == 'value']
        self._value_formats = values
        # last
        self.last_interface = None
        self.last_stat = self._get_stat()
        self.last_time = time()

    def currentSpeed(self):
        ns = self._get_stat()
        deltas = {}
        try:
            # time from previous check
            timedelta = time() - self.last_time

            # calculate deltas for all interfaces
            for old, new in zip(self.last_stat, ns):
                down = int(new[1]) - int(old[1])
                up = int(new[9]) - int(old[9])

                down /= timedelta
                up /= timedelta

                deltas[new[0]] = {'total': up + down, 'up': up, 'down': down, }

            # update last_ info
            self.last_stat = self._get_stat()
            self.last_time = time()

            # get the interface with max rate
            if self.sum_values:
                interface = 'sum'
                sum_up = sum([itm['up'] for _, itm in deltas.items()])
                sum_down = sum([itm['down'] for _, itm in deltas.items()])
                deltas[interface] = {'total': sum_up + sum_down, 'up': sum_up, 'down': sum_down}
            else:
                interface = max(deltas, key=lambda x: deltas[x]['total'])

            # if there is no rate - show last active interface, or hide

            # we need to check if it will be zero after it is formatted
            # with the desired unit eg MB/s
            total, _ = self.py3.format_units(
                deltas[interface]['total'], unit=self.unit, si=self.si_units
            )
            values = [float(x.format(total)) for x in self._value_formats]
            if max(values) == 0:
                interface = self.last_interface
                hide = self.hide_if_zero
            # if there is - update last_interface
            else:
                self.last_interface = interface
                hide = False

            # get the deltas into variable
            delta = deltas[interface] if interface else None

        except TypeError:
            delta = None
            interface = None
            hide = self.hide_if_zero

        response = {'cached_until': self.py3.time_in(self.cache_timeout)}

        if hide:
            response['full_text'] = ""
        elif not interface:
            response['full_text'] = self.format_no_connection
        else:
            self.py3.threshold_get_color(delta['down'], 'down')
            self.py3.threshold_get_color(delta['total'], 'total')
            self.py3.threshold_get_color(delta['up'], 'up')
            response['full_text'] = self.py3.safe_format(self.format, {
                'down': self._format_value(delta['down']),
                'total': self._format_value(delta['total']),
                'up': self._format_value(delta['up']),
                'interface': interface[:-1],
            })

        return response

    def _get_stat(self):
        """
        Get statistics from devfile in list of lists of words
        """
        def dev_filter(x):
            # get first word and remove trailing interface number
            x = x.strip().split(" ")[0][:-1]

            if x in self.interfaces_blacklist:
                return False

            if self.all_interfaces:
                return True

            if x in self.interfaces:
                return True

            return False

        # read devfile, skip two header files
        x = filter(dev_filter, open(self.devfile).readlines()[2:])

        try:
            # split info into words, filter empty ones
            return [list(filter(lambda x: x, _x.split(" "))) for _x in x]

        except StopIteration:
            return None

    def _format_value(self, value):
        """
        Return formatted string
        """
        value, unit = self.py3.format_units(value, unit=self.unit, si=self.si_units)
        return self.py3.safe_format(self.format_value, {'value': value, 'unit': unit})


if __name__ == "__main__":
    """
    Run module in test mode.
    """
    from py3status.module_test import module_test
    module_test(Py3status)

py3status/modules/net_rate.py
# -*- coding: utf-8 -*-
"""
Display network transfer rate.

Configuration parameters:
    all_interfaces: ignore self.interfaces, but not self.interfaces_blacklist
        (default True)
    cache_timeout: how often we refresh this module in seconds
        (default 2)
    devfile: location of dev file under /proc
        (default '/proc/net/dev')
    format: format of the module output
        (default '{interface}: {total}')
    format_no_connection: when there is no data transmitted from the start of the plugin
        (default '')
    format_value: format to use for values
        (default "[\?min_length=11 {value:.1f} {unit}]")
    hide_if_zero: hide indicator if rate == 0
        (default False)
    interfaces: comma separated list of interfaces to track
        (default [])
    interfaces_blacklist: comma separated list of interfaces to ignore
        (default 'lo')
    si_units: use SI units
        (default False)
    sum_values: sum values of each interface instead of taking the top one
        (default False)
    thresholds: thresholds to use for colors
        (default [(0, 'bad'), (1024, 'degraded'), (1024 * 1024, 'good')])
    unit: unit to use. If the unit contains a multiplier prefix, only this
        exact unit will ever be used
        (default "B/s")

Format placeholders:
    {down} download rate
    {interface} name of interface
    {total} total rate
    {up} upload rate

format_value placeholders:
    {unit} current unit
    {value} numeric value

Color thresholds:
    {down} Change color based on the value of down
    {total} Change color based on the value of total
    {up} Change color based on the value of up

@author shadowprince
@license Eclipse Public License

SAMPLE OUTPUT
{'full_text': 'eno1:  852.2 KiB/s'}
"""

from __future__ import division  # python2 compatibility
from time import time


class Py3status:
    """
    """
    # available configuration parameters
    all_interfaces = True
    cache_timeout = 2
    devfile = '/proc/net/dev'
    format = "{interface}: {total}"
    format_no_connection = ''
    format_value = "[\?min_length=11 {value:.1f} {unit}]"
    hide_if_zero = False
    interfaces = []
    interfaces_blacklist = 'lo'
    si_units = False
    sum_values = False
    thresholds = [(0, "bad"), (1024, "degraded"), (1024 * 1024, "good")]
    unit = "B/s"

    class Meta:

        def deprecate_function(config):
            # support old thresholds
            precision = config.get('precision', 1)
            padding = 3 + 1 + precision + 1 + 5
            format_value = "[\?min_length={padding} {{value:.{precision}f}} {{unit}}]".format(
                padding=padding, precision=precision
            )
            return {'format_value': format_value}

        deprecated = {
            'function': [
                {'function': deprecate_function},
            ],
            'remove': [
                {
                    'param': 'precision',
                    'msg': 'obsolete, use format_value instead',
                },
            ]
        }

    def post_config_hook(self):
        # parse some configuration parameters
        if not isinstance(self.interfaces, list):
            self.interfaces = self.interfaces.split(',')
        if not isinstance(self.interfaces_blacklist, list):
            self.interfaces_blacklist = self.interfaces_blacklist.split(',')
        placeholders = self.py3.get_placeholder_formats_list(self.format_value)
        values = ['{%s}' % x[1] for x in placeholders if x[0] == 'value']
        self._value_formats = values
        # last
        self.last_interface = None
        self.last_stat = self._get_stat()
        self.last_time = time()

    def currentSpeed(self):
        ns = self._get_stat()
        deltas = {}
        try:
            # time from previous check
            timedelta = time() - self.last_time

            # calculate deltas for all interfaces
            for old, new in zip(self.last_stat, ns):
                down = int(new[1]) - int(old[1])
                up = int(new[9]) - int(old[9])

                down /= timedelta
                up /= timedelta

                deltas[new[0]] = {'total': up + down, 'up': up, 'down': down, }

            # update last_ info
            self.last_stat = self._get_stat()
            self.last_time = time()

            # get the interface with max rate
            if self.sum_values:
                interface = 'sum'
                sum_up = sum([itm['up'] for _, itm in deltas.items()])
                sum_down = sum([itm['down'] for _, itm in deltas.items()])
                deltas[interface] = {'total': sum_up + sum_down, 'up': sum_up, 'down': sum_down}
            else:
                interface = max(deltas, key=lambda x: deltas[x]['total'])

            # if there is no rate - show last active interface, or hide

            # we need to check if it will be zero after it is formatted
            # with the desired unit eg MB/s
            total, _ = self.py3.format_units(
                deltas[interface]['total'], unit=self.unit, si=self.si_units
            )
            values = [float(x.format(total)) for x in self._value_formats]
            if max(values) == 0:
                interface = self.last_interface
                hide = self.hide_if_zero
            # if there is - update last_interface
            else:
                self.last_interface = interface
                hide = False

            # get the deltas into variable
            delta = deltas[interface] if interface else None

        except (TypeError, ValueError):
            delta = None
            interface = None
            hide = self.hide_if_zero

        response = {'cached_until': self.py3.time_in(self.cache_timeout)}

        if hide:
            response['full_text'] = ""
        elif not interface:
            response['full_text'] = self.format_no_connection
        else:
            self.py3.threshold_get_color(delta['down'], 'down')
            self.py3.threshold_get_color(delta['total'], 'total')
            self.py3.threshold_get_color(delta['up'], 'up')
            response['full_text'] = self.py3.safe_format(self.format, {
                'down': self._format_value(delta['down']),
                'total': self._format_value(delta['total']),
                'up': self._format_value(delta['up']),
                'interface': interface[:-1],
            })

        return response

    def _get_stat(self):
        """
        Get statistics from devfile in list of lists of words
        """
        def dev_filter(x):
            # get first word and remove trailing interface number
            x = x.strip().split(" ")[0][:-1]

            if x in self.interfaces_blacklist:
                return False

            if self.all_interfaces:
                return True

            if x in self.interfaces:
                return True

            return False

        # read devfile, skip two header files
        x = filter(dev_filter, open(self.devfile).readlines()[2:])

        try:
            # split info into words, filter empty ones
            return [list(filter(lambda x: x, _x.split(" "))) for _x in x]

        except StopIteration:
            return None

    def _format_value(self, value):
        """
        Return formatted string
        """
        value, unit = self.py3.format_units(value, unit=self.unit, si=self.si_units)
        return self.py3.safe_format(self.format_value, {'value': value, 'unit': unit})


if __name__ == "__main__":
    """
    Run module in test mode.
    """
    from py3status.module_test import module_test
    module_test(Py3status)

py3status/modules/net_rate.py
diff --git a/py3status/modules/net_rate.py b/py3status/modules/net_rate.py
index 309bcc35b1..8d2d38a97a 100644
--- a/py3status/modules/net_rate.py
+++ b/py3status/modules/net_rate.py
@@ -161,7 +161,7 @@ def currentSpeed(self):
             # get the deltas into variable
             delta = deltas[interface] if interface else None
 
-        except TypeError:
+        except (TypeError, ValueError):
             delta = None
             interface = None
             hide = self.hide_if_zero

Better handling of transient interfaces in net_rate
I use net_rate for a couple of tun interfaces that are transient depending on VPN connections. It would be nice if net_rate had some additional configuration on what to do for non-existent interfaces. Currently, the hardcoded text "net_rate" shows up, where ideally I'd just have no output from net_rate.

I saw "format_no_connection" - it doesn't help with this case but seems like a potential candidate to tie in to this (where no connection and no device are arguably similar).

I'll look into implementing this, but figured I'd park the feature request here for now.
