12094.2.1
by Henning Eggers
Moved tarfile helper into services. |
1 |
# Copyright 2010 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
||
4 |
"""Helpers to work with tar files more easily."""
|
|
5 |
||
6 |
__metaclass__ = type |
|
7 |
||
8 |
__all__ = [ |
|
9 |
'LaunchpadWriteTarFile', |
|
10 |
]
|
|
11 |
||
12 |
import os |
|
13 |
from StringIO import StringIO |
|
14 |
import tarfile |
|
15 |
import tempfile |
|
16 |
import time |
|
17 |
||
18 |
# A note about tarballs, StringIO and unicode. SQLObject returns unicode
|
|
19 |
# values for columns which are declared as StringCol. We have to be careful
|
|
20 |
# not to pass unicode instances to the tarfile module, because when the
|
|
21 |
# tarfile's filehandle is a StringIO object, the StringIO object gets upset
|
|
22 |
# later when we ask it for its value and it tries to join together its
|
|
23 |
# buffers. This is why the tarball code is sprinkled with ".encode('ascii')".
|
|
24 |
# If we get separate StringCol and UnicodeCol column types, we won't need this
|
|
25 |
# any longer.
|
|
26 |
# -- Dafydd Harries, 2005-04-07.
|
|
27 |
||
28 |
class LaunchpadWriteTarFile: |
|
29 |
"""Convenience wrapper around the tarfile module.
|
|
30 |
||
31 |
This class makes it convenient to generate tar files in various ways.
|
|
32 |
"""
|
|
33 |
||
34 |
def __init__(self, stream): |
|
35 |
self.tarfile = tarfile.open('', 'w:gz', stream) |
|
36 |
self.closed = False |
|
37 |
||
38 |
@classmethod
|
|
39 |
def files_to_stream(cls, files): |
|
40 |
"""Turn a dictionary of files into a data stream."""
|
|
41 |
buffer = tempfile.TemporaryFile() |
|
42 |
archive = cls(buffer) |
|
43 |
archive.add_files(files) |
|
44 |
archive.close() |
|
45 |
buffer.seek(0) |
|
46 |
return buffer |
|
47 |
||
48 |
@classmethod
|
|
49 |
def files_to_string(cls, files): |
|
50 |
"""Turn a dictionary of files into a data string."""
|
|
51 |
return cls.files_to_stream(files).read() |
|
52 |
||
53 |
@classmethod
|
|
54 |
def files_to_tarfile(cls, files): |
|
55 |
"""Turn a dictionary of files into a tarfile object."""
|
|
56 |
return tarfile.open('', 'r', cls.files_to_stream(files)) |
|
57 |
||
58 |
def close(self): |
|
59 |
"""Close the archive.
|
|
60 |
||
61 |
After the archive is closed, the data written to the filehandle will
|
|
62 |
be complete. The archive may not be appended to after it has been
|
|
63 |
closed.
|
|
64 |
"""
|
|
65 |
||
66 |
self.tarfile.close() |
|
67 |
self.closed = True |
|
68 |
||
69 |
def add_file(self, path, contents): |
|
70 |
"""Add a file to the archive."""
|
|
71 |
assert not self.closed, "Can't add a file to a closed archive" |
|
72 |
||
73 |
now = int(time.time()) |
|
74 |
path_bits = path.split(os.path.sep) |
|
75 |
||
76 |
# Ensure that all the directories in the path are present in the
|
|
77 |
# archive.
|
|
78 |
for i in range(1, len(path_bits)): |
|
79 |
joined_path = os.path.join(*path_bits[:i]) |
|
80 |
||
81 |
try: |
|
82 |
self.tarfile.getmember(joined_path) |
|
83 |
except KeyError: |
|
84 |
tarinfo = tarfile.TarInfo(joined_path) |
|
85 |
tarinfo.type = tarfile.DIRTYPE |
|
86 |
tarinfo.mtime = now |
|
87 |
tarinfo.mode = 0755 |
|
88 |
tarinfo.uname = 'launchpad' |
|
89 |
tarinfo.gname = 'launchpad' |
|
90 |
self.tarfile.addfile(tarinfo) |
|
91 |
||
92 |
tarinfo = tarfile.TarInfo(path) |
|
93 |
tarinfo.time = now |
|
94 |
tarinfo.mtime = now |
|
95 |
tarinfo.mode = 0644 |
|
96 |
tarinfo.size = len(contents) |
|
97 |
tarinfo.uname = 'launchpad' |
|
98 |
tarinfo.gname = 'launchpad' |
|
99 |
self.tarfile.addfile(tarinfo, StringIO(contents)) |
|
100 |
||
101 |
def add_files(self, files): |
|
102 |
"""Add a number of files to the archive.
|
|
103 |
||
104 |
:param files: A dictionary mapping file names to file contents.
|
|
105 |
"""
|
|
106 |
||
107 |
for filename in sorted(files.keys()): |
|
108 |
self.add_file(filename, files[filename]) |