#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import print_function import io import json import subprocess import time from .exceptions import SanityCheckError class AlfredParser: alfred_json = 'alfred-json' alfred_datatypes = [('static', 158), ('dynamic', 159)] def __str__(self): types = ['{1}=>{0}'.format(x[0], x[1]) for x in self.alfred_datatypes] return 'AlfredParser \'{0}\' {1}'.format( self.alfred_json, str.join(' ', types), ) def sanitycheck(self): """ Checks that the necessary tool 'alfred-json' is callable and outputs syntactically correct JSON data. """ testdata = None try: first_datatype = int(self.alfred_datatypes[0][1]) cmd = [self.alfred_json, '-z', '-r', str(first_datatype)] testdata = subprocess.check_output(cmd) except Exception as err: raise SanityCheckError( self, "alfred-json not found or incompatible", err) try: json.loads(testdata) except Exception as err: raise SanityCheckError( self, "alfred-json does not return valid JSON data", err) return True def fetch(self, alfred_dump=None): """Fetches the current data from alfred-json.""" data = {} timestamp = int(time.time()) alfreddata = {} for datatype in self.alfred_datatypes: # call 'alfred-json' and fetch the output cmd = [self.alfred_json, '-z', '-r', str(int(datatype[1]))] rawdata = subprocess.check_output(cmd) # convert raw input into unicode rawdata = unicode(rawdata, encoding='latin1', errors='replace') # parse it as JSON newdata = json.loads(rawdata) for item in newdata: if not item in alfreddata: alfreddata[item] = {} alfreddata[item][datatype[0]] = newdata[item] if alfred_dump is not None: jsondata = json.dumps(alfreddata, ensure_ascii=False) dumpfile = io.open(alfred_dump, 'w') dumpfile.write(jsondata) dumpfile.close() for alfredid in alfreddata: alfredinfo = alfreddata[alfredid] nodestatic = alfredinfo.get('static', {}) if len(nodestatic) == 0: print('Warning: ALFRED data for {id} does not contain static data.'.format(id=alfredid)) myid = nodestatic.get('node_id', alfredid.lower().replace(':', '')) nodeinfo = { 'hostname': None, 'mac': None, 'software': {}, 'statistics': {}, '__UPDATED__': {'alfred': timestamp}, '__RAW__': {'alfred': alfredinfo}, } data[myid] = nodeinfo if 'hostname' in nodestatic: nodeinfo['hostname'] = nodestatic['hostname'] if 'network' in nodestatic: if 'mac' in nodestatic['network']: nodeinfo['mac'] = nodestatic['network']['mac'] if 'mesh_interfaces' in nodestatic['network']: macs = [x for x in nodestatic['network']['mesh_interfaces']] nodeinfo['macs'] = macs elif 'mesh' in nodestatic['network']: ifaces = nodestatic['network']['mesh'] iftypes = [iftype for iftype in [ifaces[ifname]['interfaces'] for ifname in ifaces]] tmp = [x for x in iftypes] macs = [] for x in tmp: for iftype in x: for mac in x[iftype]: macs.append(mac) nodeinfo['macs'] = macs else: nodeinfo['macs'] = [] if 'hardware' in nodestatic: hardware = nodestatic['hardware'] nodeinfo['hardware'] = hardware.get('model') if 'software' in nodestatic: software = nodestatic['software'] nodeinfo['software']['firmware'] = \ software.get('firmware', {}).get('release') autoupdater = software.get('autoupdater', {}) autoupdater_enabled = autoupdater.get('enabled', False) nodeinfo['software']['autoupdater'] = \ autoupdater.get('branch') if autoupdater_enabled else 'off' nodedyn = alfredinfo.get('dynamic', nodestatic.get('statistics', {})) if 'uptime' in nodedyn: nodeinfo['uptime'] = int(float(nodedyn['uptime'])) if 'gateway' in nodedyn: nodeinfo['gateway'] = nodedyn['gateway'] traffic = nodedyn["traffic"] if "traffic" in nodedyn else None if traffic is not None: if not 'traffic' in nodeinfo['statistics']: nodeinfo['statistics']['traffic'] = {} item = nodeinfo['statistics']['traffic'] item['rxbytes'] = int(traffic["rx"]["bytes"]) item['txbytes'] = int(traffic["tx"]["bytes"]) if 'location' in nodestatic: nodeinfo['location'] = nodestatic['location'] return data if __name__ == "__main__": parser = AlfredParser() try: parser.sanitycheck() except SanityCheckError as err: print('SANITY-CHECK failed:', str(err)) import sys sys.exit(1) adata = parser.fetch() print(json.dumps(adata))