--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/borgend/instance.py Sun Jan 28 11:54:46 2018 +0000 @@ -0,0 +1,135 @@ +# +# Borgend borg launcher / processor +# + +import os +import json +import logging +from subprocess import Popen, PIPE + +from . import loggers +from .config import settings + +logger=loggers.get(__name__) + +necessary_opts=['--log-json', '--progress'] + +necessary_opts_for={ + 'create': ['--json'], + 'info': ['--json'], + 'list': ['--json'], +} + +# Conversion of config into command line +def arglistify(args): + flatten=lambda l: [item for sublist in l for item in sublist] + if args is None: + return [] + else: + return flatten([['--' + key, str(d[key])] for d in args for key in d]) + +class BorgInstance: + def __init__(self, operation, archive_or_repository, + common_params, op_params, paths): + self.operation=operation; + self.archive_or_repository=archive_or_repository; + self.common_params=common_params + self.op_params=op_params + self.paths=paths + + def construct_cmdline(self): + cmd=([settings['borg']['executable']]+necessary_opts+ + arglistify(self.common_params)+ + [self.operation]) + + if self.operation in necessary_opts_for: + cmd=cmd+necessary_opts_for[self.operation] + + return (cmd+arglistify(self.op_params) + +[self.archive_or_repository]+self.paths) + + def launch(self, passphrase=None): + cmd=self.construct_cmdline() + + logger.info('Launching ' + str(cmd)) + + # Set passphrase if not, or set to empty if not known, so borg + # won't hang waiting for it, which seems to happen even if we + # close stdin. + env=os.environ.copy() + env['BORG_PASSPHRASE']=passphrase or '' + + # Workaround: if launched is a standalone app created with py2app, + # borg will fail unless Python environment is reset. + # TODO: Of course, this will fail if the system needs the variables + # PYTHONPATH or PYTHONHOME set to certain values. + if '_PY2APP_LAUNCHED_' in env: + val=env['_PY2APP_LAUNCHED_'] + if val=='1': + del env['PYTHONPATH'] + del env['PYTHONHOME'] + + self.proc=Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, stdin=PIPE) + + # We don't do passphrase input etc. + self.proc.stdin.close() + + def read_result(self): + stream=self.proc.stdout + line=stream.read(-1) + if line==b'': + logger.debug('Borg stdout pipe EOF?') + return None + + try: + return json.loads(line) + except Exception as err: + logger.warning('JSON parse failed on: "%s"' % line) + return None + + def read_log(self): + stream=self.proc.stderr + try: + line=stream.readline() + except err: + logger.debug('Pipe read failed: %s' % str(err)) + + return {'type': 'log_message', + 'levelname': 'CRITICAL', + 'name': 'borgend.instance.BorgInstance', + 'msgid': 'Borgend.Exception', + 'message': err} + + if line==b'': + + logger.debug('Borg stderr pipe EOF?') + + return None + + try: + res=json.loads(line) + if 'type' not in res: + res['type']='UNKNOWN' + return res + except: + logger.debug('JSON parse failed on: "%s"' % str(line)) + + errmsg=line + for line in iter(stream.readline, b''): + errmsg=errmsg+line + + return {'type': 'log_message', + 'levelname': 'ERROR', + 'name': 'borgend.instance.BorgInstance', + 'msgid': 'Borgend.JSONFail', + 'message': str(errmsg)} + + def terminate(self): + self.proc.terminate() + + def wait(self): + return self.proc.wait() is not None + + def has_terminated(self): + return self.proc.poll() is not None +