~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

(mbp) merge bzr.dev to 0.8, prepare for release

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
19
19
This is a fairly thin wrapper on regular file IO."""
20
20
 
21
21
import os
22
 
import errno
23
22
import shutil
 
23
import sys
24
24
from stat import ST_MODE, S_ISDIR, ST_SIZE
25
25
import tempfile
26
26
import urllib
27
27
 
28
28
from bzrlib.trace import mutter
29
 
from bzrlib.transport import Transport, register_transport, \
30
 
    TransportError, NoSuchFile, FileExists
31
 
from bzrlib.osutils import abspath
32
 
 
33
 
class LocalTransportError(TransportError):
34
 
    pass
 
29
from bzrlib.transport import Transport, Server
 
30
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename, 
 
31
                            check_legal_path, rmtree)
35
32
 
36
33
 
37
34
class LocalTransport(Transport):
40
37
    def __init__(self, base):
41
38
        """Set the base path where files will be stored."""
42
39
        if base.startswith('file://'):
43
 
            base = base[7:]
 
40
            base = base[len('file://'):]
44
41
        # realpath is incompatible with symlinks. When we traverse
45
42
        # up we might be able to normpath stuff. RBC 20051003
46
 
        super(LocalTransport, self).__init__(
47
 
            os.path.normpath(abspath(base)))
 
43
        base = normpath(abspath(base))
 
44
        if base[-1] != '/':
 
45
            base = base + '/'
 
46
        super(LocalTransport, self).__init__(base)
48
47
 
49
48
    def should_cache(self):
50
49
        return False
60
59
            return LocalTransport(self.abspath(offset))
61
60
 
62
61
    def abspath(self, relpath):
63
 
        """Return the full url to the given relative URL.
64
 
        This can be supplied with a string or a list
65
 
        """
66
 
        assert isinstance(relpath, basestring)
67
 
        return os.path.join(self.base, urllib.unquote(relpath))
 
62
        """Return the full url to the given relative URL."""
 
63
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
64
        result = normpath(pathjoin(self.base, urllib.unquote(relpath)))
 
65
        #if result[-1] != '/':
 
66
        #    result += '/'
 
67
        return result
68
68
 
69
69
    def relpath(self, abspath):
70
70
        """Return the local path portion from a given absolute path.
71
71
        """
72
 
        from bzrlib.osutils import relpath
 
72
        from bzrlib.osutils import relpath, strip_trailing_slash
73
73
        if abspath is None:
74
 
            abspath = '.'
75
 
        return relpath(self.base, abspath)
 
74
            abspath = u'.'
 
75
 
 
76
        return relpath(strip_trailing_slash(self.base), 
 
77
                       strip_trailing_slash(abspath))
76
78
 
77
79
    def has(self, relpath):
78
80
        return os.access(self.abspath(relpath), os.F_OK)
85
87
        try:
86
88
            path = self.abspath(relpath)
87
89
            return open(path, 'rb')
88
 
        except IOError,e:
89
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
90
 
                raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
91
 
            raise LocalTransportError(orig_error=e)
 
90
        except (IOError, OSError),e:
 
91
            self._translate_error(e, path)
92
92
 
93
 
    def put(self, relpath, f):
 
93
    def put(self, relpath, f, mode=None):
94
94
        """Copy the file-like or string object into the location.
95
95
 
96
96
        :param relpath: Location to put the contents, relative to base.
98
98
        """
99
99
        from bzrlib.atomicfile import AtomicFile
100
100
 
 
101
        path = relpath
101
102
        try:
102
103
            path = self.abspath(relpath)
103
 
            fp = AtomicFile(path, 'wb')
104
 
        except IOError, e:
105
 
            if e.errno == errno.ENOENT:
106
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
107
 
            raise LocalTransportError(orig_error=e)
 
104
            check_legal_path(path)
 
105
            fp = AtomicFile(path, 'wb', new_mode=mode)
 
106
        except (IOError, OSError),e:
 
107
            self._translate_error(e, path)
108
108
        try:
109
109
            self._pump(f, fp)
110
110
            fp.commit()
113
113
 
114
114
    def iter_files_recursive(self):
115
115
        """Iter the relative paths of files in the transports sub-tree."""
116
 
        queue = list(self.list_dir('.'))
 
116
        queue = list(self.list_dir(u'.'))
117
117
        while queue:
118
118
            relpath = queue.pop(0)
119
119
            st = self.stat(relpath)
123
123
            else:
124
124
                yield relpath
125
125
 
126
 
    def mkdir(self, relpath):
 
126
    def mkdir(self, relpath, mode=None):
127
127
        """Create a directory at the given path."""
 
128
        path = relpath
128
129
        try:
129
 
            os.mkdir(self.abspath(relpath))
130
 
        except OSError,e:
131
 
            if e.errno == errno.EEXIST:
132
 
                raise FileExists(orig_error=e)
133
 
            elif e.errno == errno.ENOENT:
134
 
                raise NoSuchFile(orig_error=e)
135
 
            raise LocalTransportError(orig_error=e)
 
130
            path = self.abspath(relpath)
 
131
            os.mkdir(path)
 
132
            if mode is not None:
 
133
                os.chmod(path, mode)
 
134
        except (IOError, OSError),e:
 
135
            self._translate_error(e, path)
136
136
 
137
 
    def append(self, relpath, f):
 
137
    def append(self, relpath, f, mode=None):
138
138
        """Append the text in the file-like object into the final
139
139
        location.
140
140
        """
141
 
        fp = open(self.abspath(relpath), 'ab')
 
141
        try:
 
142
            fp = open(self.abspath(relpath), 'ab')
 
143
            if mode is not None:
 
144
                os.chmod(self.abspath(relpath), mode)
 
145
        except (IOError, OSError),e:
 
146
            self._translate_error(e, relpath)
 
147
        # win32 workaround (tell on an unwritten file returns 0)
 
148
        fp.seek(0, 2)
 
149
        result = fp.tell()
142
150
        self._pump(f, fp)
 
151
        return result
143
152
 
144
153
    def copy(self, rel_from, rel_to):
145
154
        """Copy the item at rel_from to the location at rel_to"""
146
 
        import shutil
147
155
        path_from = self.abspath(rel_from)
148
156
        path_to = self.abspath(rel_to)
149
157
        try:
150
158
            shutil.copy(path_from, path_to)
151
 
        except OSError,e:
152
 
            raise LocalTransportError(orig_error=e)
 
159
        except (IOError, OSError),e:
 
160
            # TODO: What about path_to?
 
161
            self._translate_error(e, path_from)
 
162
 
 
163
    def rename(self, rel_from, rel_to):
 
164
        path_from = self.abspath(rel_from)
 
165
        try:
 
166
            # *don't* call bzrlib.osutils.rename, because we want to 
 
167
            # detect errors on rename
 
168
            os.rename(path_from, self.abspath(rel_to))
 
169
        except (IOError, OSError),e:
 
170
            # TODO: What about path_to?
 
171
            self._translate_error(e, path_from)
153
172
 
154
173
    def move(self, rel_from, rel_to):
155
174
        """Move the item at rel_from to the location at rel_to"""
157
176
        path_to = self.abspath(rel_to)
158
177
 
159
178
        try:
160
 
            os.rename(path_from, path_to)
161
 
        except OSError,e:
162
 
            raise LocalTransportError(orig_error=e)
 
179
            # this version will delete the destination if necessary
 
180
            rename(path_from, path_to)
 
181
        except (IOError, OSError),e:
 
182
            # TODO: What about path_to?
 
183
            self._translate_error(e, path_from)
163
184
 
164
185
    def delete(self, relpath):
165
186
        """Delete the item at relpath"""
 
187
        path = relpath
166
188
        try:
167
 
            os.remove(self.abspath(relpath))
168
 
        except OSError,e:
169
 
            raise LocalTransportError(orig_error=e)
 
189
            path = self.abspath(relpath)
 
190
            os.remove(path)
 
191
        except (IOError, OSError),e:
 
192
            # TODO: What about path_to?
 
193
            self._translate_error(e, path)
170
194
 
171
 
    def copy_to(self, relpaths, other, pb=None):
 
195
    def copy_to(self, relpaths, other, mode=None, pb=None):
172
196
        """Copy a set of entries from self into another Transport.
173
197
 
174
198
        :param relpaths: A list/generator of entries to be copied.
177
201
            # Both from & to are on the local filesystem
178
202
            # Unfortunately, I can't think of anything faster than just
179
203
            # copying them across, one by one :(
180
 
            import shutil
181
 
 
182
204
            total = self._get_total(relpaths)
183
205
            count = 0
184
206
            for path in relpaths:
185
207
                self._update_pb(pb, 'copy-to', count, total)
186
 
                shutil.copy(self.abspath(path), other.abspath(path))
 
208
                try:
 
209
                    mypath = self.abspath(path)
 
210
                    otherpath = other.abspath(path)
 
211
                    shutil.copy(mypath, otherpath)
 
212
                    if mode is not None:
 
213
                        os.chmod(otherpath, mode)
 
214
                except (IOError, OSError),e:
 
215
                    self._translate_error(e, path)
187
216
                count += 1
188
217
            return count
189
218
        else:
190
 
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
 
219
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
191
220
 
192
221
    def listable(self):
193
222
        """See Transport.listable."""
198
227
        WARNING: many transports do not support this, so trying avoid using
199
228
        it if at all possible.
200
229
        """
 
230
        path = self.abspath(relpath)
201
231
        try:
202
 
            return os.listdir(self.abspath(relpath))
203
 
        except OSError,e:
204
 
            raise LocalTransportError(orig_error=e)
 
232
            return [urllib.quote(entry) for entry in os.listdir(path)]
 
233
        except (IOError, OSError), e:
 
234
            self._translate_error(e, path)
205
235
 
206
236
    def stat(self, relpath):
207
237
        """Return the stat information for a file.
208
238
        """
 
239
        path = relpath
209
240
        try:
210
 
            return os.stat(self.abspath(relpath))
211
 
        except OSError,e:
212
 
            raise LocalTransportError(orig_error=e)
 
241
            path = self.abspath(relpath)
 
242
            return os.stat(path)
 
243
        except (IOError, OSError),e:
 
244
            self._translate_error(e, path)
213
245
 
214
246
    def lock_read(self, relpath):
215
247
        """Lock the given file for shared (read) access.
216
248
        :return: A lock object, which should be passed to Transport.unlock()
217
249
        """
218
250
        from bzrlib.lock import ReadLock
219
 
        return ReadLock(self.abspath(relpath))
 
251
        path = relpath
 
252
        try:
 
253
            path = self.abspath(relpath)
 
254
            return ReadLock(path)
 
255
        except (IOError, OSError), e:
 
256
            self._translate_error(e, path)
220
257
 
221
258
    def lock_write(self, relpath):
222
259
        """Lock the given file for exclusive (write) access.
227
264
        from bzrlib.lock import WriteLock
228
265
        return WriteLock(self.abspath(relpath))
229
266
 
 
267
    def rmdir(self, relpath):
 
268
        """See Transport.rmdir."""
 
269
        path = relpath
 
270
        try:
 
271
            path = self.abspath(relpath)
 
272
            os.rmdir(path)
 
273
        except (IOError, OSError),e:
 
274
            self._translate_error(e, path)
 
275
 
 
276
    def _can_roundtrip_unix_modebits(self):
 
277
        if sys.platform == 'win32':
 
278
            # anyone else?
 
279
            return False
 
280
        else:
 
281
            return True
 
282
 
230
283
 
231
284
class ScratchTransport(LocalTransport):
232
285
    """A transport that works in a temporary dir and cleans up after itself.
241
294
        super(ScratchTransport, self).__init__(base)
242
295
 
243
296
    def __del__(self):
244
 
        shutil.rmtree(self.base, ignore_errors=True)
 
297
        rmtree(self.base, ignore_errors=True)
245
298
        mutter("%r destroyed" % self)
 
299
 
 
300
 
 
301
class LocalRelpathServer(Server):
 
302
    """A pretend server for local transports, using relpaths."""
 
303
 
 
304
    def get_url(self):
 
305
        """See Transport.Server.get_url."""
 
306
        return "."
 
307
 
 
308
 
 
309
class LocalAbspathServer(Server):
 
310
    """A pretend server for local transports, using absolute paths."""
 
311
 
 
312
    def get_url(self):
 
313
        """See Transport.Server.get_url."""
 
314
        return os.path.abspath("")
 
315
 
 
316
 
 
317
class LocalURLServer(Server):
 
318
    """A pretend server for local transports, using file:// urls."""
 
319
 
 
320
    def get_url(self):
 
321
        """See Transport.Server.get_url."""
 
322
        # FIXME: \ to / on windows
 
323
        return "file://%s" % os.path.abspath("")
 
324
 
 
325
 
 
326
def get_test_permutations():
 
327
    """Return the permutations to be used in testing."""
 
328
    return [(LocalTransport, LocalRelpathServer),
 
329
            (LocalTransport, LocalAbspathServer),
 
330
            (LocalTransport, LocalURLServer),
 
331
            ]