~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Martin Pool
  • Date: 2006-01-30 06:23:50 UTC
  • mfrom: (1534.1.17 integration)
  • Revision ID: mbp@sourcefrog.net-20060130062350-d6f25277ddcdfd79
[merge] robert's integration of much recent work

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
"""Implementation of Transport for the local filesystem.
17
 
"""
18
 
 
19
 
from bzrlib.transport import Transport, register_transport, \
20
 
    TransportError, NoSuchFile, FileExists
21
 
import os, errno
22
 
 
23
 
class LocalTransportError(TransportError):
24
 
    pass
 
16
 
 
17
"""Transport for the local filesystem.
 
18
 
 
19
This is a fairly thin wrapper on regular file IO."""
 
20
 
 
21
import os
 
22
import shutil
 
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
 
24
import tempfile
 
25
import urllib
 
26
 
 
27
from bzrlib.trace import mutter
 
28
from bzrlib.transport import Transport, Server
 
29
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
 
30
 
25
31
 
26
32
class LocalTransport(Transport):
27
33
    """This is the transport agent for local filesystem access."""
32
38
            base = base[7:]
33
39
        # realpath is incompatible with symlinks. When we traverse
34
40
        # up we might be able to normpath stuff. RBC 20051003
35
 
        super(LocalTransport, self).__init__(
36
 
            os.path.normpath(os.path.abspath(base)))
 
41
        base = normpath(abspath(base))
 
42
        if base[-1] != '/':
 
43
            base = base + '/'
 
44
        super(LocalTransport, self).__init__(base)
37
45
 
38
46
    def should_cache(self):
39
47
        return False
49
57
            return LocalTransport(self.abspath(offset))
50
58
 
51
59
    def abspath(self, relpath):
52
 
        """Return the full url to the given relative path.
 
60
        """Return the full url to the given relative URL.
53
61
        This can be supplied with a string or a list
54
62
        """
55
 
        if isinstance(relpath, basestring):
56
 
            relpath = [relpath]
57
 
        return os.path.join(self.base, *relpath)
 
63
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
64
        return pathjoin(self.base, urllib.unquote(relpath))
58
65
 
59
66
    def relpath(self, abspath):
60
67
        """Return the local path portion from a given absolute path.
61
68
        """
62
 
        from bzrlib.branch import _relpath
63
 
        return _relpath(self.base, abspath)
 
69
        from bzrlib.osutils import relpath
 
70
        if abspath is None:
 
71
            abspath = u'.'
 
72
        if abspath.endswith('/'):
 
73
            abspath = abspath[:-1]
 
74
        return relpath(self.base[:-1], abspath)
64
75
 
65
76
    def has(self, relpath):
66
77
        return os.access(self.abspath(relpath), os.F_OK)
73
84
        try:
74
85
            path = self.abspath(relpath)
75
86
            return open(path, 'rb')
76
 
        except IOError,e:
77
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
78
 
                raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
79
 
            raise LocalTransportError(orig_error=e)
80
 
 
81
 
    def get_partial(self, relpath, start, length=None):
82
 
        """Get just part of a file.
83
 
 
84
 
        :param relpath: Path to the file, relative to base
85
 
        :param start: The starting position to read from
86
 
        :param length: The length to read. A length of None indicates
87
 
                       read to the end of the file.
88
 
        :return: A file-like object containing at least the specified bytes.
89
 
                 Some implementations may return objects which can be read
90
 
                 past this length, but this is not guaranteed.
91
 
        """
92
 
        # LocalTransport.get_partial() doesn't care about the length
93
 
        # argument, because it is using a local file, and thus just
94
 
        # returns the file seek'ed to the appropriate location.
95
 
        try:
96
 
            path = self.abspath(relpath)
97
 
            f = open(path, 'rb')
98
 
            f.seek(start, 0)
99
 
            return f
100
 
        except IOError,e:
101
 
            if e.errno == errno.ENOENT:
102
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
103
 
            raise LocalTransportError(orig_error=e)
104
 
 
105
 
    def put(self, relpath, f):
 
87
        except (IOError, OSError),e:
 
88
            self._translate_error(e, path)
 
89
 
 
90
    def put(self, relpath, f, mode=None):
106
91
        """Copy the file-like or string object into the location.
107
92
 
108
93
        :param relpath: Location to put the contents, relative to base.
110
95
        """
111
96
        from bzrlib.atomicfile import AtomicFile
112
97
 
 
98
        path = relpath
113
99
        try:
114
100
            path = self.abspath(relpath)
115
 
            fp = AtomicFile(path, 'wb')
116
 
        except IOError, e:
117
 
            if e.errno == errno.ENOENT:
118
 
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
119
 
            raise LocalTransportError(orig_error=e)
 
101
            fp = AtomicFile(path, 'wb', new_mode=mode)
 
102
        except (IOError, OSError),e:
 
103
            self._translate_error(e, path)
120
104
        try:
121
105
            self._pump(f, fp)
122
106
            fp.commit()
123
107
        finally:
124
108
            fp.close()
125
109
 
126
 
    def mkdir(self, relpath):
 
110
    def iter_files_recursive(self):
 
111
        """Iter the relative paths of files in the transports sub-tree."""
 
112
        queue = list(self.list_dir(u'.'))
 
113
        while queue:
 
114
            relpath = urllib.quote(queue.pop(0))
 
115
            st = self.stat(relpath)
 
116
            if S_ISDIR(st[ST_MODE]):
 
117
                for i, basename in enumerate(self.list_dir(relpath)):
 
118
                    queue.insert(i, relpath+'/'+basename)
 
119
            else:
 
120
                yield relpath
 
121
 
 
122
    def mkdir(self, relpath, mode=None):
127
123
        """Create a directory at the given path."""
 
124
        path = relpath
128
125
        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)
 
126
            path = self.abspath(relpath)
 
127
            os.mkdir(path)
 
128
            if mode is not None:
 
129
                os.chmod(path, mode)
 
130
        except (IOError, OSError),e:
 
131
            self._translate_error(e, path)
136
132
 
137
133
    def append(self, relpath, f):
138
134
        """Append the text in the file-like object into the final
139
135
        location.
140
136
        """
141
 
        fp = open(self.abspath(relpath), 'ab')
 
137
        try:
 
138
            fp = open(self.abspath(relpath), 'ab')
 
139
        except (IOError, OSError),e:
 
140
            self._translate_error(e, relpath)
142
141
        self._pump(f, fp)
143
142
 
144
143
    def copy(self, rel_from, rel_to):
148
147
        path_to = self.abspath(rel_to)
149
148
        try:
150
149
            shutil.copy(path_from, path_to)
151
 
        except OSError,e:
152
 
            raise LocalTransportError(orig_error=e)
 
150
        except (IOError, OSError),e:
 
151
            # TODO: What about path_to?
 
152
            self._translate_error(e, path_from)
153
153
 
154
154
    def move(self, rel_from, rel_to):
155
155
        """Move the item at rel_from to the location at rel_to"""
157
157
        path_to = self.abspath(rel_to)
158
158
 
159
159
        try:
160
 
            os.rename(path_from, path_to)
161
 
        except OSError,e:
162
 
            raise LocalTransportError(orig_error=e)
 
160
            rename(path_from, path_to)
 
161
        except (IOError, OSError),e:
 
162
            # TODO: What about path_to?
 
163
            self._translate_error(e, path_from)
163
164
 
164
165
    def delete(self, relpath):
165
166
        """Delete the item at relpath"""
 
167
        path = relpath
166
168
        try:
167
 
            os.remove(self.abspath(relpath))
168
 
        except OSError,e:
169
 
            raise LocalTransportError(orig_error=e)
 
169
            path = self.abspath(relpath)
 
170
            os.remove(path)
 
171
        except (IOError, OSError),e:
 
172
            # TODO: What about path_to?
 
173
            self._translate_error(e, path)
170
174
 
171
 
    def copy_to(self, relpaths, other, pb=None):
 
175
    def copy_to(self, relpaths, other, mode=None, pb=None):
172
176
        """Copy a set of entries from self into another Transport.
173
177
 
174
178
        :param relpaths: A list/generator of entries to be copied.
183
187
            count = 0
184
188
            for path in relpaths:
185
189
                self._update_pb(pb, 'copy-to', count, total)
186
 
                shutil.copy(self.abspath(path), other.abspath(path))
 
190
                try:
 
191
                    mypath = self.abspath(path)
 
192
                    otherpath = other.abspath(path)
 
193
                    shutil.copy(mypath, otherpath)
 
194
                    if mode is not None:
 
195
                        os.chmod(otherpath, mode)
 
196
                except (IOError, OSError),e:
 
197
                    self._translate_error(e, path)
187
198
                count += 1
188
199
            return count
189
200
        else:
190
 
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
 
201
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
191
202
 
192
203
    def listable(self):
193
204
        """See Transport.listable."""
198
209
        WARNING: many transports do not support this, so trying avoid using
199
210
        it if at all possible.
200
211
        """
 
212
        path = relpath
201
213
        try:
202
 
            return os.listdir(self.abspath(relpath))
203
 
        except OSError,e:
204
 
            raise LocalTransportError(orig_error=e)
 
214
            path = self.abspath(relpath)
 
215
            return os.listdir(path)
 
216
        except (IOError, OSError),e:
 
217
            self._translate_error(e, path)
205
218
 
206
219
    def stat(self, relpath):
207
220
        """Return the stat information for a file.
208
221
        """
 
222
        path = relpath
209
223
        try:
210
 
            return os.stat(self.abspath(relpath))
211
 
        except OSError,e:
212
 
            raise LocalTransportError(orig_error=e)
 
224
            path = self.abspath(relpath)
 
225
            return os.stat(path)
 
226
        except (IOError, OSError),e:
 
227
            self._translate_error(e, path)
213
228
 
214
229
    def lock_read(self, relpath):
215
230
        """Lock the given file for shared (read) access.
216
231
        :return: A lock object, which should be passed to Transport.unlock()
217
232
        """
218
233
        from bzrlib.lock import ReadLock
219
 
        return ReadLock(self.abspath(relpath))
 
234
        path = relpath
 
235
        try:
 
236
            path = self.abspath(relpath)
 
237
            return ReadLock(path)
 
238
        except (IOError, OSError), e:
 
239
            self._translate_error(e, path)
220
240
 
221
241
    def lock_write(self, relpath):
222
242
        """Lock the given file for exclusive (write) access.
227
247
        from bzrlib.lock import WriteLock
228
248
        return WriteLock(self.abspath(relpath))
229
249
 
230
 
# If nothing else matches, try the LocalTransport
231
 
register_transport(None, LocalTransport)
232
 
register_transport('file://', LocalTransport)
 
250
 
 
251
class ScratchTransport(LocalTransport):
 
252
    """A transport that works in a temporary dir and cleans up after itself.
 
253
    
 
254
    The dir only exists for the lifetime of the Python object.
 
255
    Obviously you should not put anything precious in it.
 
256
    """
 
257
 
 
258
    def __init__(self, base=None):
 
259
        if base is None:
 
260
            base = tempfile.mkdtemp()
 
261
        super(ScratchTransport, self).__init__(base)
 
262
 
 
263
    def __del__(self):
 
264
        shutil.rmtree(self.base, ignore_errors=True)
 
265
        mutter("%r destroyed" % self)
 
266
 
 
267
 
 
268
class LocalRelpathServer(Server):
 
269
    """A pretend server for local transports, using relpaths."""
 
270
 
 
271
    def get_url(self):
 
272
        """See Transport.Server.get_url."""
 
273
        return "."
 
274
 
 
275
 
 
276
class LocalAbspathServer(Server):
 
277
    """A pretend server for local transports, using absolute paths."""
 
278
 
 
279
    def get_url(self):
 
280
        """See Transport.Server.get_url."""
 
281
        return os.path.abspath("")
 
282
 
 
283
 
 
284
class LocalURLServer(Server):
 
285
    """A pretend server for local transports, using file:// urls."""
 
286
 
 
287
    def get_url(self):
 
288
        """See Transport.Server.get_url."""
 
289
        # FIXME: \ to / on windows
 
290
        return "file://%s" % os.path.abspath("")
 
291
 
 
292
 
 
293
def get_test_permutations():
 
294
    """Return the permutations to be used in testing."""
 
295
    return [(LocalTransport, LocalRelpathServer),
 
296
            (LocalTransport, LocalAbspathServer),
 
297
            (LocalTransport, LocalURLServer),
 
298
            ]