diff --git a/.gitignore b/.gitignore index 6920eb1..6665c70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -plugins/custom*.py *.iml *.swp *.wpr diff --git a/conf.yaml b/conf.yaml index b65cc52..7416c97 100644 --- a/conf.yaml +++ b/conf.yaml @@ -1,14 +1,4 @@ -# Typical plugin properties: -# - name: plugin name (always required, all other properties are optional) -# note that a plugin can be used more than once (i.e. different -# timezones in the date plugin) -# - title: title representation on the panel -# - hide_ok: false to force display value even if it is below problem threshold -# - problem: a threshold value to make a value urgent and / or display -# the value on the panel - output_format: i3 - plugins: - name: ping title: NET @@ -16,27 +6,15 @@ plugins: - de-ber-as20647.anchors.atlas.ripe.net - nl-ams-as1101.anchors.atlas.ripe.net - uk-boh-as196745.anchors.atlas.ripe.net - hide_ok: false - - name: disk partition: / problem: 80 hide_ok: false - - name: disk partition: /home problem: 90 - +- name: pacman - name: mem - - name: load - -- name: batt - - name: date format: '%a %d %H:%M' - -- name: date - title: UTC - tz: UTC - format: '%H:%M' diff --git a/plugins/__init__.py b/plugins/__init__.py index 262d144..68df0c9 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -2,33 +2,30 @@ import threading import time -PLUGIN_DEFAULTS = {'freq': 1, 'hide_ok': True} +def parse_config(config, defaults): + result = dict() + for key in defaults: + result[key] = config[key] if key in config else defaults[key] + return result class PluginThreadCommon: - def __init__(self, config, defaults=None): + def __init__(self, config, defaults=dict()): + if 'freq' not in defaults: + defaults['freq'] = 1 + if 'hide_ok' not in defaults: + defaults['hide_ok'] = True + self.conf = parse_config(config, defaults) self.status = dict() - self.conf = dict() - self.conf.update(PLUGIN_DEFAULTS) - if defaults: - self.conf.update(defaults) - self.conf.update(config) self.hide = False self.thread = threading.Thread(target=self.run) self.thread.daemon = True - def format_status(self, status, urgent=False): - if 'title' in self.conf and self.conf['title']: - full_text = '{}: {}'.format(self.conf['title'], status) - else: - full_text = status - self.status.update({'full_text': full_text, 'urgent': urgent}) - def start(self): self.thread.start() def main(self): - pass + self.status['full_text'] = 'placeholder' def run(self): while True: diff --git a/plugins/batt.py b/plugins/batt.py index 0876e6a..90c52ea 100644 --- a/plugins/batt.py +++ b/plugins/batt.py @@ -1,31 +1,29 @@ import plugins -BATT_DEFAULTS = { - 'title': 'BAT', - 'problem': 15, - 'symbol_charging': '\u2191', - 'symbol_discharging': '\u2193' -} +BATTERY_DIR = '/sys/class/power_supply/BAT0/' class PluginThread(plugins.PluginThreadCommon): def __init__(self, config): - super(PluginThread, self).__init__(config, BATT_DEFAULTS) + defaults = { + 'problem': 15, + 'symbol_charging': '\u2191', + 'symbol_discharging': '\u2193' + } + super(PluginThread, self).__init__(config, defaults) def main(self): with \ - open('/sys/class/power_supply/BAT0/capacity', 'r') as bcap, \ - open('/sys/class/power_supply/BAT0/status', 'r') as bstat: - capacity = bcap.readline().strip() - status_value = bstat.readline().strip() - - if status_value != 'Discharging': + open(BATTERY_DIR + 'capacity', 'r') as batt_capacity, \ + open(BATTERY_DIR + 'status', 'r') as batt_status: + status = batt_status.read().strip() + capacity = batt_capacity.read().strip() + if status != 'Discharging': symbol = self.conf['symbol_charging'] - urgent = False + self.status['urgent'] = False else: symbol = self.conf['symbol_discharging'] - urgent = float(capacity) <= self.conf['problem'] + self.status['urgent'] = float(capacity) <= self.conf['problem'] - status = '{}% {}'.format(capacity, symbol) - self.format_status(status, urgent) + self.status['full_text'] = 'BAT: ' + capacity + '% ' + symbol diff --git a/plugins/cmd.py b/plugins/cmd.py deleted file mode 100644 index dcf29f3..0000000 --- a/plugins/cmd.py +++ /dev/null @@ -1,23 +0,0 @@ -import plugins -import subprocess - - -PACMAN_DEFAULTS = { - 'cmd': ('/usr/bin/echo', 'I am cmd'), - 'title': 'CMD', 'freq': 15 -} - - -class PluginThread(plugins.PluginThreadCommon): - def __init__(self, config): - super(PluginThread, self).__init__(config, PACMAN_DEFAULTS) - - def main(self): - proc = subprocess.Popen( - self.conf['cmd'], stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, - encoding='UTF-8' - ) - out = proc.communicate()[0].strip().splitlines()[0] - - self.format_status(out) diff --git a/plugins/date.py b/plugins/date.py index 08e8601..ee6009a 100644 --- a/plugins/date.py +++ b/plugins/date.py @@ -2,19 +2,15 @@ import datetime import plugins -DATE_DEFAULTS = {'format': '%c'} - - class PluginThread(plugins.PluginThreadCommon): def __init__(self, config): - super(PluginThread, self).__init__(config, DATE_DEFAULTS) + defaults = {'format': '%c', 'tz': None} + super(PluginThread, self).__init__(config, defaults) self.timezone = None - if 'tz' in self.conf: + if self.conf['tz']: import pytz self.timezone = pytz.timezone(self.conf['tz']) def main(self): - now = datetime.datetime\ - .now(tz=self.timezone)\ - .strftime(self.conf['format']) - self.format_status(now) + now = datetime.datetime.now(tz=self.timezone) + self.status['full_text'] = now.strftime(self.conf['format']) diff --git a/plugins/disk.py b/plugins/disk.py index 7797447..6ab9c2b 100644 --- a/plugins/disk.py +++ b/plugins/disk.py @@ -2,14 +2,10 @@ import plugins import psutil -DISK_DEFAULTS = {'partition': '/', 'problem': 80, 'freq': 15} - - class PluginThread(plugins.PluginThreadCommon): def __init__(self, config): - super(PluginThread, self).__init__(config, DISK_DEFAULTS) - if 'title' not in self.conf: - self.conf['title'] = self.conf['partition'] + defaults = {'partition': '/', 'problem': 80, 'freq': 15} + super(PluginThread, self).__init__(config, defaults) def main(self): du_stat = psutil.disk_usage(self.conf['partition']) @@ -19,6 +15,6 @@ class PluginThread(plugins.PluginThreadCommon): else: self.hide = True self.status['urgent'] = False - - status = '{:.2f}G'.format(du_stat.free / 2**30) - self.format_status(status) + du_free = str(round(du_stat.free / 2**30, 2)) + disk_usage = self.conf['partition'] + ': ' + du_free + 'G' + self.status['full_text'] = disk_usage diff --git a/plugins/fortune.py b/plugins/fga.py similarity index 58% rename from plugins/fortune.py rename to plugins/fga.py index 40b3f1d..ecc5857 100644 --- a/plugins/fortune.py +++ b/plugins/fga.py @@ -3,30 +3,27 @@ import requests import time -FORTUNE_DEFAULTS = { - 'uri': 'http://fucking-great-advice.ru/api/random', - 'freq': 120, 'retry': 3 -} - - +URI = 'http://fucking-great-advice.ru/api/random' class PluginThread(plugins.PluginThreadCommon): def __init__(self, config): - super(PluginThread, self).__init__(config, FORTUNE_DEFAULTS) + defaults = {'freq': 120, 'retry': 3} + super(PluginThread, self).__init__(config, defaults) self.retry = False + def main(self): try: - req = requests.get(self.conf['uri'], timeout=2) - fortune = req.json()['text'] if req.status_code == 200 else 'N/A' + req = requests.get(URI, timeout=2) + advice = req.json()['text'] if req.status_code == 200 else 'N/A' self.retry = False except requests.exceptions.Timeout: - fortune = 'N/A (timeout)' + advice = 'N/A (timeout)' self.retry = True except requests.exceptions.ConnectionError: - fortune = 'N/A (offline)' + advice = 'N/A (offline)' self.retry = True + self.status['full_text'] = advice - self.format_status(fortune) def run(self): while True: diff --git a/plugins/filecat.py b/plugins/filecat.py deleted file mode 100644 index 0b6d82a..0000000 --- a/plugins/filecat.py +++ /dev/null @@ -1,24 +0,0 @@ -import plugins - - -FILECAT_DEFAULTS = { - 'filename': '/etc/hostname', 'title': 'CAT', - 'freq': 60, 'nofile': 'unavailable' -} - - -class PluginThread(plugins.PluginThreadCommon): - def __init__(self, config): - super(PluginThread, self).__init__(config, FILECAT_DEFAULTS) - self.hide = False - - def main(self): - try: - with open(self.conf['filename'], 'r') as datafile: - contents = datafile.read().strip() - urgent = False - except FileNotFoundError: - contents = self.conf['nofile'] - urgent = True - - self.format_status(contents, urgent) diff --git a/plugins/load.py b/plugins/load.py index 082ff47..8567fdd 100644 --- a/plugins/load.py +++ b/plugins/load.py @@ -2,21 +2,17 @@ import os import plugins -LOAD_DEFAULTS = {'title': 'LOAD', 'freq': 20, 'problem': 1} - - class PluginThread(plugins.PluginThreadCommon): def __init__(self, config): - super(PluginThread, self).__init__(config, LOAD_DEFAULTS) + defaults = {'freq': 20, 'problem': 1} + super(PluginThread, self).__init__(config, defaults) def main(self): loads = os.getloadavg() if loads[0] >= self.conf['problem']: self.hide = False - urgent = True + self.status['urgent'] = True else: self.hide = True - urgent = False - status = '{:.2f} {:.2f} {:.2f}'.format(*loads) - - self.format_status(status, urgent) + self.status['urgent'] = False + self.status['full_text'] = 'LA: {:.2f} {:.2f} {:.2f}'.format(*loads) diff --git a/plugins/mem.py b/plugins/mem.py index 1463377..14c1b16 100644 --- a/plugins/mem.py +++ b/plugins/mem.py @@ -2,21 +2,18 @@ import psutil import plugins -MEM_DEFAULTS = {'title': 'RAM', 'problem': 85} - - class PluginThread(plugins.PluginThreadCommon): def __init__(self, config): - super(PluginThread, self).__init__(config, MEM_DEFAULTS) + defaults = {'problem': 85} + super(PluginThread, self).__init__(config, defaults) def main(self): mem_stat = psutil.virtual_memory() if mem_stat.percent > self.conf['problem']: self.hide = False - urgent = True + self.status['urgent'] = True else: self.hide = True - urgent = False - - status = '{:.2f}G'.format(mem_stat.available / 2**30) - self.format_status(status, urgent) + self.status['urgent'] = False + mem_available = round(mem_stat.available / 2**30, 2) + self.status['full_text'] = 'RAM: {:.2f}G'.format(mem_available) diff --git a/plugins/pacman.py b/plugins/pacman.py new file mode 100644 index 0000000..ccf0550 --- /dev/null +++ b/plugins/pacman.py @@ -0,0 +1,27 @@ +import plugins +import subprocess + + +class PluginThread(plugins.PluginThreadCommon): + def __init__(self, config): + defaults = { + 'freq': 15, + 'problem': 10 + } + super(PluginThread, self).__init__(config, defaults) + self.format_status(0) + + def format_status(self, count): + self.hide = count == 0 + self.status['urgent'] = count >= self.conf['problem'] + self.status['full_text'] = 'UPD: ' + str(count) + + def main(self): + pacman_qu = subprocess.Popen( + ('/usr/bin/pacman', '-Qu'), stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, + encoding='UTF-8' + ) + out = pacman_qu.communicate()[0].strip().splitlines() + packages = [pkg for pkg in out if not '[ignored]' in pkg] + self.format_status(len(packages)) diff --git a/plugins/ping.py b/plugins/ping.py index 983a234..2f0bb42 100644 --- a/plugins/ping.py +++ b/plugins/ping.py @@ -1,26 +1,29 @@ import os -import subprocess import random import plugins -PING_DEFAULTS = { - 'hosts': tuple(), 'title': 'PING', 'timeout': 150 -} - - class PluginThread(plugins.PluginThreadCommon): def __init__(self, config): - super(PluginThread, self).__init__(config, PING_DEFAULTS) - self.ping_cmd = ('fping', '-c1', '-qt' + str(self.conf['timeout'])) + defaults = {'hosts': list(), 'title': 'PING', 'timeout': 150} + super(PluginThread, self).__init__(config, defaults) + self.format_status('n/a') + + def format_status(self, state): + self.status['full_text'] = self.conf['title'] + ': ' + state + if state == 'on': + self.status['urgent'] = False + self.hide = True + else: + self.status['urgent'] = True def main(self): - host = random.choice(self.conf['hosts']) - fping = subprocess.run( - (*self.ping_cmd, host), - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL - ) - if fping.returncode == 0: - self.format_status('up') - else: - self.format_status('down', True) + random.shuffle(self.conf['hosts']) + for host in self.conf['hosts']: + fping = 'fping -qc1t' + str(self.conf['timeout'])\ + + ' ' + host + ' &>/dev/null' + response = os.system(fping) + if response == 0: + self.format_status('on') + break + self.format_status('off') diff --git a/vdstatus b/vdstatus index 88b9a91..d3f5425 100755 --- a/vdstatus +++ b/vdstatus @@ -6,6 +6,7 @@ import os import sys import time import yaml +import plugins DEFAULT_CONFIG = os.path.join(os.environ['HOME'], '.config/vdstatus/conf.yaml') @@ -27,10 +28,10 @@ def parse_arguments(): class PluginRunner: def __init__(self, config_file=DEFAULT_CONFIG): - self.conf = dict() - self.conf.update(DEFAULTS) + config = dict() with open(config_file) as config_data: - self.conf.update(yaml.safe_load(config_data)) + config = yaml.load(config_data) + self.conf = plugins.parse_config(config, DEFAULTS) self.plugins_loaded = list() self.format_output = self.format_term for plugin in self.conf['plugins']: @@ -49,9 +50,8 @@ class PluginRunner: outputs = list() for plugin in self.plugins_loaded: if \ - 'full_text' in plugin.status and ( - not plugin.conf['hide_ok'] or not plugin.hide - ): + not plugin.conf['hide_ok'] or \ + not plugin.hide: outputs.append(plugin.status) print(self.format_output(outputs), flush=True)