# -*- coding: utf-8 -*- # ***************************************************************************** # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: # Markus Zolliker # # ***************************************************************************** import sys import os from glob import glob from os.path import join, isdir, basename, expanduser, exists from configparser import ConfigParser from servicemanager.base import ServiceManager, ServiceDown, UsageError def dummy(*args, **kwds): pass class Node: description = '' def __call__(self, equipment_id, description, *args, **kwds): self.description = description class FrappyManager(ServiceManager): group = 'frappy' services = ('main', 'stick', 'addons') USAGE = """ Usage: frappy list [instance] * frappy start frappy restart [] [] * frappy stop [] * frappy listcfg [ | develop] # list available cfg files is one of main, stick, addons %s * wildcards allowed, using '.' to replace 0 or more arbitrary characters in """ def config_dirs(self, ins, service): cfgpaths = [] cfgparser = ConfigParser() cfgparser.optionxform = str cfgfile = self.env[ins].get('FRAPPY_CONFIG_FILE') confdir = self.env[ins].get('FRAPPY_CONFDIR') if cfgfile: cfgfile = self.env[ins]['FRAPPY_CONFIG_FILE'].replace('', service) cfgparser.read(cfgfile) try: section = cfgparser['FRAPPY'] except KeyError: raise ValueError('%s does not exist or has no FRAPPY section' % cfgfile) confdir = section.get('confdir', confdir) for cfgpath in confdir.split(os.pathsep): if cfgpath.endswith(''): cfgpaths.append(expanduser(cfgpath[:-6] + service)) else: scfg = join(cfgpath, service) if isdir(scfg): cfgpaths.append(scfg) cfgpaths.append(expanduser(cfgpath)) return cfgpaths def prepare_start(self, ins, service, cfg=''): start_dir, env = super().prepare_start(ins, service) for key, value in env.items(): if '' in value: env[key] = value.replace('', service) os.environ[key] = env[key] cfgpaths = self.config_dirs(ins, service) if cfgpaths: env['FRAPPY_CONFDIR'] = os.pathsep.join(cfgpaths) # merge PYTHONPATH from servicemanager.cfg with the one from environment pypathlist = [] for pypath in env.get('PYTHONPATH'), os.environ.get('PYTHONPATH'): if pypath is not None: pypathlist.extend(p for p in pypath.split(':') if p not in pypathlist) if pypathlist: env['PYTHONPATH'] = os.environ['PYTHONPATH'] = ':'.join(pypathlist) return start_dir, env def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None): if self.wildcard(ins) is not None: raise UsageError('no wildcards allowed with %s start' % self.group) if cfg and not service and len(self.services) != 1: raise UsageError('need service to start (one of %s)' % ', '.join(self.services)) super().do_start(ins, service, cfg, restart, wait, logger) def do_restart(self, ins, service=None, cfg=None, logger=None): ins_list = super().do_restart(ins, service, cfg, logger) if ins_list: # wildcard used # determine running processes with cfg cfginfo = {} self.get_procs(None, cfginfo) cfgs = {i for i, s in cfginfo if s == service or service is None} return [i for i in ins_list if i in cfgs] def get_nodes(self, ins='', service=None): start_dir = ServiceManager.prepare_start(self, ins, None)[0] if start_dir not in sys.path: sys.path.insert(0, start_dir) nodes = [] services = self.services if service is None else [service] for service in services: try: self.check_running(ins, service) nodes.append('localhost:%d' % self.info[ins][service]) except ServiceDown: if len(services) == 1: raise UsageError('frappy %s %s is not running' % (ins, service)) except KeyError: if ins: raise UsageError('unknown instance %s' % ins) raise UsageError('missing instance') if not nodes: raise UsageError(f"frappy {ins}: none of {'/'.join(services)} is running") return nodes def do_gui(self, ins='', service=None): print(f'starting frappy gui {ins} {service or ""}') nodes = self.get_nodes(ins, service) import logging from frappy.gui.qt import QApplication from frappy.gui.mainwindow import MainWindow app = QApplication([]) win = MainWindow(nodes, logging.getLogger('gui')) win.show() return app.exec_() def do_cli(self, ins='', service=None): nodes = self.get_nodes(ins, service) from frappy.client.interactive import init, interact init(*nodes) interact() def all_cfg(self, ins, service, by_dir=False): result = {} all_cfg = {} if not ins: return {} namespace = {k: dummy for k in ('Mod', 'Param', 'Command', 'Group')} namespace['Node'] = node = Node() for service in [service] if service else self.services: for cfgdir in self.config_dirs(ins, service): result.setdefault(cfgdir, {}) cfgs = result[cfgdir] root = self.env[ins].get('FRAPPY_ROOT') cfg_pattern ='*_cfg.py' if exists(join(root, 'frappy')) else '*.cfg' for cfgfile in glob(join(cfgdir, cfg_pattern)): desc = '' try: if cfgfile.endswith('.py'): node.description = '' try: with open(cfgfile, encoding='utf-8') as f: exec(f.read(), namespace) except Exception as e: node.description = repr(e) desc = node.description else: parser = ConfigParser() parser.read(cfgfile) for s in parser.sections(): if s == 'NODE' or s.startswith('node '): desc = parser[s].get('description', '').split('\n')[0] break except Exception: pass cfg = basename(cfgfile)[:1-len(cfg_pattern)] if cfg not in all_cfg: all_cfg[cfg] = desc cfgs[cfg] = desc return result if by_dir else all_cfg def do_listcfg(self, ins='', service='', prt=print): if not ins: raise UsageError('missing instance') if service: all_cfg = self.all_cfg(ins, service, by_dir=True) else: all_cfg = {} for service in self.services: all_cfg.update(self.all_cfg(ins, service, by_dir=True)) for cfgdir, cfgs in all_cfg.items(): if cfgs: prt('') prt('--- %s:' % cfgdir) keylen = max(len(k) for k in cfgs) for cfg, desc in cfgs.items(): prt('%s %s' % (cfg.ljust(keylen), desc)) def treat_args(self, argdict, unknown=(), extra=()): if len(unknown) == 1: cfg = unknown[0] if cfg == 'develop': argdict['service'] = cfg return super().treat_args(argdict, (), ()) if (',' in cfg or cfg.endswith('.cfg') or cfg in self.all_cfg(argdict.get('ins'), argdict.get('service'))): return super().treat_args(argdict, (), unknown) return super().treat_args(argdict, unknown, extra)