borgend/instance.py

changeset 80
a409242121d5
parent 79
b075b3db3044
child 86
2fe66644c50d
equal deleted inserted replaced
79:b075b3db3044 80:a409242121d5
1 #
2 # Borgend borg launcher / processor
3 #
4
5 import os
6 import json
7 import logging
8 from subprocess import Popen, PIPE
9
10 from . import loggers
11 from .config import settings
12
13 logger=loggers.get(__name__)
14
15 necessary_opts=['--log-json', '--progress']
16
17 necessary_opts_for={
18 'create': ['--json'],
19 'info': ['--json'],
20 'list': ['--json'],
21 }
22
23 # Conversion of config into command line
24 def arglistify(args):
25 flatten=lambda l: [item for sublist in l for item in sublist]
26 if args is None:
27 return []
28 else:
29 return flatten([['--' + key, str(d[key])] for d in args for key in d])
30
31 class BorgInstance:
32 def __init__(self, operation, archive_or_repository,
33 common_params, op_params, paths):
34 self.operation=operation;
35 self.archive_or_repository=archive_or_repository;
36 self.common_params=common_params
37 self.op_params=op_params
38 self.paths=paths
39
40 def construct_cmdline(self):
41 cmd=([settings['borg']['executable']]+necessary_opts+
42 arglistify(self.common_params)+
43 [self.operation])
44
45 if self.operation in necessary_opts_for:
46 cmd=cmd+necessary_opts_for[self.operation]
47
48 return (cmd+arglistify(self.op_params)
49 +[self.archive_or_repository]+self.paths)
50
51 def launch(self, passphrase=None):
52 cmd=self.construct_cmdline()
53
54 logger.info('Launching ' + str(cmd))
55
56 # Set passphrase if not, or set to empty if not known, so borg
57 # won't hang waiting for it, which seems to happen even if we
58 # close stdin.
59 env=os.environ.copy()
60 env['BORG_PASSPHRASE']=passphrase or ''
61
62 # Workaround: if launched is a standalone app created with py2app,
63 # borg will fail unless Python environment is reset.
64 # TODO: Of course, this will fail if the system needs the variables
65 # PYTHONPATH or PYTHONHOME set to certain values.
66 if '_PY2APP_LAUNCHED_' in env:
67 val=env['_PY2APP_LAUNCHED_']
68 if val=='1':
69 del env['PYTHONPATH']
70 del env['PYTHONHOME']
71
72 self.proc=Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, stdin=PIPE)
73
74 # We don't do passphrase input etc.
75 self.proc.stdin.close()
76
77 def read_result(self):
78 stream=self.proc.stdout
79 line=stream.read(-1)
80 if line==b'':
81 logger.debug('Borg stdout pipe EOF?')
82 return None
83
84 try:
85 return json.loads(line)
86 except Exception as err:
87 logger.warning('JSON parse failed on: "%s"' % line)
88 return None
89
90 def read_log(self):
91 stream=self.proc.stderr
92 try:
93 line=stream.readline()
94 except err:
95 logger.debug('Pipe read failed: %s' % str(err))
96
97 return {'type': 'log_message',
98 'levelname': 'CRITICAL',
99 'name': 'borgend.instance.BorgInstance',
100 'msgid': 'Borgend.Exception',
101 'message': err}
102
103 if line==b'':
104
105 logger.debug('Borg stderr pipe EOF?')
106
107 return None
108
109 try:
110 res=json.loads(line)
111 if 'type' not in res:
112 res['type']='UNKNOWN'
113 return res
114 except:
115 logger.debug('JSON parse failed on: "%s"' % str(line))
116
117 errmsg=line
118 for line in iter(stream.readline, b''):
119 errmsg=errmsg+line
120
121 return {'type': 'log_message',
122 'levelname': 'ERROR',
123 'name': 'borgend.instance.BorgInstance',
124 'msgid': 'Borgend.JSONFail',
125 'message': str(errmsg)}
126
127 def terminate(self):
128 self.proc.terminate()
129
130 def wait(self):
131 return self.proc.wait() is not None
132
133 def has_terminated(self):
134 return self.proc.poll() is not None
135

mercurial