~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Robert Collins
  • Date: 2005-10-18 05:26:22 UTC
  • mto: This revision was merged to the branch mainline in revision 1463.
  • Revision ID: robertc@robertcollins.net-20051018052622-653d638c9e26fde4
fix broken tests

Show diffs side-by-side

added added

removed removed

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