1import logging
2import os
3import subprocess
4
5from borgmatic.execute import DO_NOT_CAPTURE, execute_command
6
7logger = logging.getLogger(__name__)
8
9
10def extract_last_archive_dry_run(repository, lock_wait=None, local_path='borg', remote_path=None):
11    '''
12    Perform an extraction dry-run of the most recent archive. If there are no archives, skip the
13    dry-run.
14    '''
15    remote_path_flags = ('--remote-path', remote_path) if remote_path else ()
16    lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else ()
17    verbosity_flags = ()
18    if logger.isEnabledFor(logging.DEBUG):
19        verbosity_flags = ('--debug', '--show-rc')
20    elif logger.isEnabledFor(logging.INFO):
21        verbosity_flags = ('--info',)
22
23    full_list_command = (
24        (local_path, 'list', '--short')
25        + remote_path_flags
26        + lock_wait_flags
27        + verbosity_flags
28        + (repository,)
29    )
30
31    list_output = execute_command(
32        full_list_command, output_log_level=None, borg_local_path=local_path
33    )
34
35    try:
36        last_archive_name = list_output.strip().splitlines()[-1]
37    except IndexError:
38        return
39
40    list_flag = ('--list',) if logger.isEnabledFor(logging.DEBUG) else ()
41    full_extract_command = (
42        (local_path, 'extract', '--dry-run')
43        + remote_path_flags
44        + lock_wait_flags
45        + verbosity_flags
46        + list_flag
47        + (
48            '{repository}::{last_archive_name}'.format(
49                repository=repository, last_archive_name=last_archive_name
50            ),
51        )
52    )
53
54    execute_command(full_extract_command, working_directory=None)
55
56
57def extract_archive(
58    dry_run,
59    repository,
60    archive,
61    paths,
62    location_config,
63    storage_config,
64    local_path='borg',
65    remote_path=None,
66    destination_path=None,
67    strip_components=None,
68    progress=False,
69    extract_to_stdout=False,
70):
71    '''
72    Given a dry-run flag, a local or remote repository path, an archive name, zero or more paths to
73    restore from the archive, location/storage configuration dicts, optional local and remote Borg
74    paths, and an optional destination path to extract to, extract the archive into the current
75    directory.
76
77    If extract to stdout is True, then start the extraction streaming to stdout, and return that
78    extract process as an instance of subprocess.Popen.
79    '''
80    umask = storage_config.get('umask', None)
81    lock_wait = storage_config.get('lock_wait', None)
82
83    if progress and extract_to_stdout:
84        raise ValueError('progress and extract_to_stdout cannot both be set')
85
86    full_command = (
87        (local_path, 'extract')
88        + (('--remote-path', remote_path) if remote_path else ())
89        + (('--numeric-owner',) if location_config.get('numeric_owner') else ())
90        + (('--umask', str(umask)) if umask else ())
91        + (('--lock-wait', str(lock_wait)) if lock_wait else ())
92        + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
93        + (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
94        + (('--dry-run',) if dry_run else ())
95        + (('--strip-components', str(strip_components)) if strip_components else ())
96        + (('--progress',) if progress else ())
97        + (('--stdout',) if extract_to_stdout else ())
98        + ('::'.join((repository if ':' in repository else os.path.abspath(repository), archive)),)
99        + (tuple(paths) if paths else ())
100    )
101
102    # The progress output isn't compatible with captured and logged output, as progress messes with
103    # the terminal directly.
104    if progress:
105        return execute_command(
106            full_command, output_file=DO_NOT_CAPTURE, working_directory=destination_path
107        )
108        return None
109
110    if extract_to_stdout:
111        return execute_command(
112            full_command,
113            output_file=subprocess.PIPE,
114            working_directory=destination_path,
115            run_to_completion=False,
116        )
117
118    # Don't give Borg local path, so as to error on warnings, as Borg only gives a warning if the
119    # restore paths don't exist in the archive!
120    execute_command(full_command, working_directory=destination_path)
121