~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/canonical/archivepublisher/pool.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-03-17 20:41:13 UTC
  • mfrom: (3277.1.4 launchpad-foobar2)
  • Revision ID: pqm@pqm.ubuntu.com-20060317204113-9841a4470db3611b
[r=jamesh] Mainline soyuz

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
 
10
10
from sets import Set
11
11
import os
 
12
import tempfile
 
13
 
12
14
 
13
15
class Poolifier(object):
14
16
    """The Poolifier takes a (source name, component) tuple and tells you
62
64
                return p[0], p[2], p[3]
63
65
            return p[0], p[2], None
64
66
 
 
67
 
 
68
def relative_symlink(src_path, dst_path):
 
69
    """os.symlink replacement that creates relative symbolic links."""
 
70
    path_sep = os.path.sep
 
71
    src_path = os.path.normpath(src_path)
 
72
    dst_path = os.path.normpath(dst_path)
 
73
    src_path_elems = src_path.split(path_sep)
 
74
    dst_path_elems = dst_path.split(path_sep)
 
75
    if os.path.isabs(src_path):
 
76
        if not os.path.isabs(dst_path):
 
77
            dst_path = os.path.abspath(dst_path)
 
78
        # XXX: dsilvers: 20060315: Note that os.path.commonprefix does not
 
79
        # require that the common prefix be full path elements. As a result
 
80
        # the common prefix of /foo/bar/baz and /foo/barbaz is /foo/bar.
 
81
        # This isn't an issue here in the pool code but it could be a
 
82
        # problem if this code is transplanted elsewhere.
 
83
        common_prefix = os.path.commonprefix([src_path_elems, dst_path_elems])
 
84
        backward_elems = ['..'] * (len(dst_path_elems)-len(common_prefix)-1)
 
85
        forward_elems = src_path_elems[len(common_prefix):]
 
86
        src_path = path_sep.join(backward_elems + forward_elems)
 
87
    os.symlink(src_path, dst_path)
 
88
 
 
89
class _diskpool_atomicfile:
 
90
    """Simple file-like object used by the pool to atomically move into place
 
91
    a file after downloading from the librarian.
 
92
 
 
93
    This class is designed to solve a very specific problem encountered in
 
94
    the publisher. Namely that should the publisher crash during the process
 
95
    of publishing a file to the pool, an empty or incomplete file would be
 
96
    present in the pool. Its mere presence would fool the publisher into
 
97
    believing it had already downloaded that file to the pool, resulting
 
98
    in failures in the apt-ftparchive stage.
 
99
 
 
100
    By performing a rename() when the file is guaranteed to have been
 
101
    fully written to disk (after the fd.close()) we can be sure that if
 
102
    the filename is present in the pool, it is definitely complete.
 
103
    """
 
104
 
 
105
    def __init__(self, targetfilename, mode, rootpath="/tmp"):
 
106
        if mode == "w":
 
107
            mode = "wb"
 
108
        assert mode == "wb"
 
109
        self.targetfilename = targetfilename
 
110
        fd, name = tempfile.mkstemp(prefix=".temp-download.", dir=rootpath)
 
111
        self.fd = os.fdopen(fd, mode)
 
112
        self.tempname = name
 
113
        self.write = self.fd.write
 
114
 
 
115
    def close(self):
 
116
        """Make the atomic move into place having closed the temp file."""
 
117
        self.fd.close()
 
118
        os.chmod(self.tempname, 0644)
 
119
        # Note that this will fail if the target and the temp dirs are on
 
120
        # different filesystems.
 
121
        os.rename(self.tempname, self.targetfilename)
 
122
 
 
123
 
65
124
class AlreadyInPool:
66
125
    """Raised when an attempt is made to add a file already in the pool."""
67
126
 
 
127
 
68
128
class NotInPool:
69
129
    """Raised when an attempt is made to remove a non-existent file."""
70
130
 
 
131
 
71
132
class DiskPoolEntry:
72
133
    def __init__(self, source=''):
73
134
        self.defcomp = ''
74
135
        self.comps = Set()
75
136
        self.source = source
76
137
 
 
138
 
77
139
class DiskPool:
78
140
    """Scan a pool on the filesystem and record information about it."""
79
141
 
149
211
                                           sourcename, leafname)
150
212
                if not os.path.exists(os.path.dirname(targetpath)):
151
213
                    os.makedirs(os.path.dirname(targetpath))
152
 
                os.symlink(sourcepath, targetpath)
 
214
                relative_symlink(sourcepath, targetpath)
153
215
                self.files[leafname].comps.add(component)
154
216
                self.components[component][leafname] = True
155
217
            raise AlreadyInPool()
162
224
        self.components[component][leafname] = False
163
225
        self.files[leafname] = DiskPoolEntry(sourcename)
164
226
        self.files[leafname].defcomp = component
165
 
        return file(targetpath,"w")
 
227
        return _diskpool_atomicfile(targetpath, "wb", rootpath=self.rootpath)
166
228
    
167
229
    def removeFile(self, component, sourcename, leafname):
168
230
        """Remove a file from a given component.
234
296
            except OSError:
235
297
                # Do nothing because it's almost certainly a not found
236
298
                pass
237
 
            os.symlink(targetpath, newpath)
 
299
            relative_symlink(targetpath, newpath)
238
300
 
239
301
    def sanitiseLinks(self, preferredcomponents):
240
302
        """Go through the files and ensure that wherever a file is in more