borgend/instance.py

changeset 80
a409242121d5
parent 79
b075b3db3044
child 86
2fe66644c50d
--- /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
+

mercurial