3 # Copyright (C) 2013 Intra2net AG
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Lesser General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see
17 # <http://www.gnu.org/licenses/lgpl-3.0.html>
23 from functools import partial
25 from deltatar import tarfile
28 def rescue(tar_files, rescue_dir=None):
30 Rescues a multivolume tarfile. Checks file name extension to detect
31 format (compression, etc). Assumes it to be multivolume tar.
34 if isinstance(tar_files, basestring):
35 tar_files = [tar_files]
37 if not isinstance(tar_files, list):
38 raise Exception("tar_files must be a list")
41 if not isinstance(f, basestring):
42 raise Exception("tar_files must be a list of strings")
43 if not os.path.exists(f):
44 raise Exception("tar file '%s' doesn't exist" % f)
46 if rescue_dir is None:
47 rescue_dir = os.path.dirname(tar_files[0])
48 elif rescue_dir is None:
49 rescue_dir = tempfile.mkdtemp()
51 # autodetect file type by extension
52 first_tar_file = tar_files[0]
53 if first_tar_file.endswith(".tar.gz"):
55 elif first_tar_file.endswith(".tar"):
58 base_name = os.path.basename(first_tar_file)
59 extract_files = tar_files
61 # num the number of files used in rescue mode. Used to name those files
62 # when creating them. We put num in an object so that it can be referenced
63 # instead of copied inside new_gz partial
66 # divide in compressed tar block files if it's r#gz
69 # function used to create each chunk file
70 def new_gz(context, extract_files, prefix, i):
71 path = "%s.%d" %(prefix, context['num'])
72 extract_files.append(path)
74 return open(path, 'w')
75 new_gz = partial(new_gz, context, extract_files)
77 # split in compressed chunks
79 filesplit.split_file('\x1f\x8b',
80 os.path.join(rescue_dir, base_name), f, new_gz)
82 # includes volumes already extracted with new_volume_handler
83 already_extracted_vols = []
85 def new_volume_handler(already_extracted_vols, next_num, tarobj, base_name, volume_number):
87 Handles the new volumes when extracting
90 # handle the special case where the first file is whatever.tar.gz and
91 # the second is whatever.tar.gz.0
92 base_name_split = base_name.split('.')
95 next_num = int(base_name_split[-1]) + 1
96 base_name = ".".join(base_name_split[:-1])
97 except ValueError as e:
100 volume_path = "%s.%d" % (base_name, next_num)
101 already_extracted_vols.append(volume_path)
102 tarobj.open_volume(volume_path)
104 new_volume_handler = partial(new_volume_handler, already_extracted_vols)
106 # extract files, as much as possible
107 for f in extract_files:
108 if f in already_extracted_vols:
111 tarobj = tarfile.TarFile.open(f, mode=mode,
112 new_volume_handler=new_volume_handler)
118 if __name__ == "__main__":
119 parser = argparse.ArgumentParser()
121 parser.add_argument("--rescue_dir", help="directory where rescue files "
122 "should be created. /tmp by default")
123 parser.add_argument("tar_files", nargs="+", help="list of files of a "
124 "multitar file to rescue. Assumes format first.extension "
125 "second.extension.0 third.extension.1 ...")
127 args = parser.parse_args()
128 rescue(tar_files=args.tar_files, rescue_dir=args.rescue_dir)