summaryrefslogtreecommitdiff
path: root/lbup/repository.py
blob: 8275910850a1a02da5039f6c8699d3af732fbc4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import fcntl
import logging
import os
import os.path
import subprocess

class StepResult:
    success = None
    output  = None
    def __init__(self, success = True, output = ''):
        self.success = success
        self.output  = output

class BackupResult:
    target_results = None
    par2_result    = None

    def __init__(self):
        self.target_results = {}
        self.par2_result    = StepResult()

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/lbup
    """
    bup_dir   = None
    data_dir  = None
    lock_name = 'lock'

    _logger   = None

    def __init__(self, bup_dir = None, data_dir = None, logger = 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/lbup/')

        if logger is None:
            self._logger = logging.getLogger('%s.%s' % (self.__class__.__name__, bup_dir))
        else:
            self._logger = logger

        # 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, dry_run = False):
        """
        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()

            self._logger.debug('Acquiring repository lock')
            fcntl.lockf(lockfile, fcntl.LOCK_EX)
            try:
                for tgt in tgts:
                    self._logger.info('Backing up %s...' % tgt.name)
                    try:
                        res = tgt.save(self.data_dir, dry_run)
                    except Exception as e:
                        self._logger.exception('Exception backing up %s: %s' % (tgt.name, str(e)))
                        res = StepResult(False, str(e).encode('utf-8'))
                    else:
                        self._logger.info('Backing up %s done' % tgt.name)

                    result.target_results[tgt.name] = res

                if gen_par2:
                    self._logger.info('Generating par2 files...')
                    res = subprocess.run(['bup', 'fsck', '-g'],
                                         capture_output = True)
                    self._logger.info('Generating par2 files done')

                    result.par2_result = StepResult(res.returncode == 0,
                                                    res.stderr + res.stdout)
            finally:
                self._logger.debug('Releasing repository lock')
                fcntl.lockf(lockfile, fcntl.LOCK_UN)

            self._logger.debug('Backup finished')

            return result