]> arthur.barton.de Git - bup.git/blob - lib/bup/cmd/import_duplicity.py
import-duplicity: convert to internal command
[bup.git] / lib / bup / cmd / import_duplicity.py
1
2 from __future__ import absolute_import
3 from calendar import timegm
4 from pipes import quote
5 from subprocess import check_call
6 from time import strftime, strptime
7 import os, sys, tempfile
8
9 from bup import git, helpers, options
10 from bup.compat import argv_bytes, str_type
11 from bup.helpers import (handle_ctrl_c,
12                          log,
13                          readpipe,
14                          shstr,
15                          saved_errors,
16                          unlink)
17 import bup.path
18
19
20 optspec = """
21 bup import-duplicity [-n] <duplicity-source-url> <bup-save-name>
22 --
23 n,dry-run  don't do anything; just print what would be done
24 """
25
26 dry_run = False
27
28 def logcmd(cmd):
29     log(shstr(cmd).decode(errors='backslashreplace') + '\n')
30
31 def exc(cmd, shell=False):
32     logcmd(cmd)
33     if not dry_run:
34         check_call(cmd, shell=shell)
35
36 def exo(cmd, shell=False, preexec_fn=None, close_fds=True):
37     logcmd(cmd)
38     if not dry_run:
39         return helpers.exo(cmd, shell=shell, preexec_fn=preexec_fn,
40                            close_fds=close_fds)[0]
41
42 def redirect_dup_output():
43     os.dup2(1, 3)
44     os.dup2(1, 2)
45
46
47 def main(argv):
48     global dry_run
49
50     log('\nbup: import-duplicity is EXPERIMENTAL (proceed with caution)\n\n')
51
52     o = options.Options(optspec)
53     opt, flags, extra = o.parse_bytes(argv[1:])
54     dry_run = opt.dry_run
55
56     if len(extra) < 1 or not extra[0]:
57         o.fatal('duplicity source URL required')
58     if len(extra) < 2 or not extra[1]:
59         o.fatal('bup destination save name required')
60     if len(extra) > 2:
61         o.fatal('too many arguments')
62
63     source_url, save_name = extra
64     source_url = argv_bytes(source_url)
65     save_name = argv_bytes(save_name)
66     bup_path = bup.path.exe()
67
68     git.check_repo_or_die()
69
70     tmpdir = tempfile.mkdtemp(prefix=b'bup-import-dup-')
71     try:
72         dup = [b'duplicity', b'--archive-dir', tmpdir + b'/dup-cache']
73         restoredir = tmpdir + b'/restore'
74         tmpidx = tmpdir + b'/index'
75
76         collection_status = \
77             exo(dup + [b'collection-status', b'--log-fd=3', source_url],
78                 close_fds=False, preexec_fn=redirect_dup_output)  # i.e. 3>&1 1>&2
79         # Duplicity output lines of interest look like this (one leading space):
80         #  full 20150222T073111Z 1 noenc
81         #  inc 20150222T073233Z 1 noenc
82         dup_timestamps = []
83         for line in collection_status.splitlines():
84             if line.startswith(b' inc '):
85                 assert(len(line) >= len(b' inc 20150222T073233Z'))
86                 dup_timestamps.append(line[5:21])
87             elif line.startswith(b' full '):
88                 assert(len(line) >= len(b' full 20150222T073233Z'))
89                 dup_timestamps.append(line[6:22])
90         for i, dup_ts in enumerate(dup_timestamps):
91             tm = strptime(dup_ts.decode('ascii'), '%Y%m%dT%H%M%SZ')
92             exc([b'rm', b'-rf', restoredir])
93             exc(dup + [b'restore', b'-t', dup_ts, source_url, restoredir])
94             exc([bup_path, b'index', b'-uxf', tmpidx, restoredir])
95             exc([bup_path, b'save', b'--strip', b'--date', b'%d' % timegm(tm),
96                  b'-f', tmpidx, b'-n', save_name, restoredir])
97         sys.stderr.flush()
98     finally:
99         exc([b'rm', b'-rf', tmpdir])
100
101     if saved_errors:
102         log('warning: %d errors encountered\n' % len(saved_errors))
103         sys.exit(1)