import fcntl import os import os.path import subprocess class StepResult: retcode = None output = None def __init__(self, retcode = 0, output = None): self.retcode = retcode self.output = output class BackupResult: target_results = None par2_result = None def __init__(self): self.target_results = [] self.par2_result = StepResult() @property def all_ok(self): return (all(map(lambda tgtres: tgtres.retcode == 0, self.target_results)) and self.par2_result.retcode == 0) class Repo: """ A single Bup repository into which the data will be backed up, plus a separate directory for extra runtime data. :param str bup_dir: path to the bup repository, defaults to BUP_DIR or ~/.bup :param str data_dir: path to the directory for storing the runtime data, defaults to ~/.local/var/bupper """ bup_dir = None data_dir = None lock_name = 'lock' def __init__(self, bup_dir = None, data_dir = None): if bup_dir is None: if 'BUP_DIR' in os.environ: bup_dir = os.environ['BUP_DIR'] else: bup_dir = os.path.expanduser('~/.bup') if data_dir is None: data_dir = os.path.expanduser('~/.local/var/bupper/') # create the data dir, if it does not already exist os.makedirs(data_dir, 0o700, exist_ok = True) self.bup_dir = bup_dir self.data_dir = data_dir def backup(self, tgts, gen_par2 = True): """ Backup the supplied targets. :param list of Target tgts: List of targets to back up. :param bool gen_par2: Whether to generate par2 recovery information after the backup concludes' """ with open(os.path.join(self.data_dir, self.lock_name), 'w') as lockfile: result = BackupResult() fcntl.lockf(lockfile, fcntl.LOCK_EX) try: for tgt in tgts: res = tgt.save(self.data_dir) result.target_results.append(res) if gen_par2: res = subprocess.run(['bup', 'fsck', '-g'], capture_output = True) result.par2_result = StepResult(res.returncode, res.stderr + res.stdout) finally: fcntl.lockf(lockfile, fcntl.LOCK_UN) return result