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