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
99
100
101
102
103
104
105
106
107
108
|
# Copyright 2010 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Helpers to work with tar files more easily."""
__metaclass__ = type
__all__ = [
'LaunchpadWriteTarFile',
]
import os
from StringIO import StringIO
import tarfile
import tempfile
import time
# A note about tarballs, StringIO and unicode. SQLObject returns unicode
# values for columns which are declared as StringCol. We have to be careful
# not to pass unicode instances to the tarfile module, because when the
# tarfile's filehandle is a StringIO object, the StringIO object gets upset
# later when we ask it for its value and it tries to join together its
# buffers. This is why the tarball code is sprinkled with ".encode('ascii')".
# If we get separate StringCol and UnicodeCol column types, we won't need this
# any longer.
# -- Dafydd Harries, 2005-04-07.
class LaunchpadWriteTarFile:
"""Convenience wrapper around the tarfile module.
This class makes it convenient to generate tar files in various ways.
"""
def __init__(self, stream):
self.tarfile = tarfile.open('', 'w:gz', stream)
self.closed = False
@classmethod
def files_to_stream(cls, files):
"""Turn a dictionary of files into a data stream."""
buffer = tempfile.TemporaryFile()
archive = cls(buffer)
archive.add_files(files)
archive.close()
buffer.seek(0)
return buffer
@classmethod
def files_to_string(cls, files):
"""Turn a dictionary of files into a data string."""
return cls.files_to_stream(files).read()
@classmethod
def files_to_tarfile(cls, files):
"""Turn a dictionary of files into a tarfile object."""
return tarfile.open('', 'r', cls.files_to_stream(files))
def close(self):
"""Close the archive.
After the archive is closed, the data written to the filehandle will
be complete. The archive may not be appended to after it has been
closed.
"""
self.tarfile.close()
self.closed = True
def add_file(self, path, contents):
"""Add a file to the archive."""
assert not self.closed, "Can't add a file to a closed archive"
now = int(time.time())
path_bits = path.split(os.path.sep)
# Ensure that all the directories in the path are present in the
# archive.
for i in range(1, len(path_bits)):
joined_path = os.path.join(*path_bits[:i])
try:
self.tarfile.getmember(joined_path)
except KeyError:
tarinfo = tarfile.TarInfo(joined_path)
tarinfo.type = tarfile.DIRTYPE
tarinfo.mtime = now
tarinfo.mode = 0755
tarinfo.uname = 'launchpad'
tarinfo.gname = 'launchpad'
self.tarfile.addfile(tarinfo)
tarinfo = tarfile.TarInfo(path)
tarinfo.time = now
tarinfo.mtime = now
tarinfo.mode = 0644
tarinfo.size = len(contents)
tarinfo.uname = 'launchpad'
tarinfo.gname = 'launchpad'
self.tarfile.addfile(tarinfo, StringIO(contents))
def add_files(self, files):
"""Add a number of files to the archive.
:param files: A dictionary mapping file names to file contents.
"""
for filename in sorted(files.keys()):
self.add_file(filename, files[filename])
|