~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Martin Pool
  • Date: 2005-05-17 06:56:16 UTC
  • Revision ID: mbp@sourcefrog.net-20050517065616-6f23381d6184a8aa
- add space for un-merged patches

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
 
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
 
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
 
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
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.
20
 
"""
21
 
 
22
 
import os
23
 
import shutil
24
 
import sys
25
 
from stat import ST_MODE, S_ISDIR, ST_SIZE
26
 
import tempfile
27
 
 
28
 
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename, 
29
 
                            check_legal_path, rmtree)
30
 
from bzrlib.symbol_versioning import warn
31
 
from bzrlib.trace import mutter
32
 
from bzrlib.transport import Transport, Server
33
 
import bzrlib.urlutils as urlutils
34
 
 
35
 
 
36
 
class LocalTransport(Transport):
37
 
    """This is the transport agent for local filesystem access."""
38
 
 
39
 
    def __init__(self, base):
40
 
        """Set the base path where files will be stored."""
41
 
        if not base.startswith('file://'):
42
 
            warn("Instantiating LocalTransport with a filesystem path"
43
 
                " is deprecated as of bzr 0.8."
44
 
                " Please use bzrlib.transport.get_transport()"
45
 
                " or pass in a file:// url.",
46
 
                 DeprecationWarning,
47
 
                 stacklevel=2
48
 
                 )
49
 
            base = urlutils.local_path_to_url(base)
50
 
        if base[-1] != '/':
51
 
            base = base + '/'
52
 
        super(LocalTransport, self).__init__(base)
53
 
        self._local_base = urlutils.local_path_from_url(base)
54
 
        ## mutter("_local_base: %r => %r", base, self._local_base)
55
 
 
56
 
    def should_cache(self):
57
 
        return False
58
 
 
59
 
    def clone(self, offset=None):
60
 
        """Return a new LocalTransport with root at self.base + offset
61
 
        Because the local filesystem does not require a connection, 
62
 
        we can just return a new object.
63
 
        """
64
 
        if offset is None:
65
 
            return LocalTransport(self.base)
66
 
        else:
67
 
            return LocalTransport(self.abspath(offset))
68
 
 
69
 
    def _abspath(self, relative_reference):
70
 
        """Return a path for use in os calls.
71
 
 
72
 
        Several assumptions are made:
73
 
         - relative_reference does not contain '..'
74
 
         - relative_reference is url escaped.
75
 
        """
76
 
        if relative_reference in ('.', ''):
77
 
            return self._local_base
78
 
        return self._local_base + urlutils.unescape(relative_reference)
79
 
 
80
 
    def abspath(self, relpath):
81
 
        """Return the full url to the given relative URL."""
82
 
        # TODO: url escape the result. RBC 20060523.
83
 
        assert isinstance(relpath, basestring), (type(relpath), relpath)
84
 
        # jam 20060426 Using normpath on the real path, because that ensures
85
 
        #       proper handling of stuff like
86
 
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
87
 
        return urlutils.local_path_to_url(path)
88
 
 
89
 
    def local_abspath(self, relpath):
90
 
        """Transform the given relative path URL into the actual path on disk
91
 
 
92
 
        This function only exists for the LocalTransport, since it is
93
 
        the only one that has direct local access.
94
 
        This is mostly for stuff like WorkingTree which needs to know
95
 
        the local working directory.
96
 
        
97
 
        This function is quite expensive: it calls realpath which resolves
98
 
        symlinks.
99
 
        """
100
 
        absurl = self.abspath(relpath)
101
 
        # mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
102
 
        return urlutils.local_path_from_url(absurl)
103
 
 
104
 
    def relpath(self, abspath):
105
 
        """Return the local path portion from a given absolute path.
106
 
        """
107
 
        if abspath is None:
108
 
            abspath = u'.'
109
 
 
110
 
        return urlutils.file_relpath(
111
 
            urlutils.strip_trailing_slash(self.base), 
112
 
            urlutils.strip_trailing_slash(abspath))
113
 
 
114
 
    def has(self, relpath):
115
 
        return os.access(self._abspath(relpath), os.F_OK)
116
 
 
117
 
    def get(self, relpath):
118
 
        """Get the file at the given relative path.
119
 
 
120
 
        :param relpath: The relative path to the file
121
 
        """
122
 
        try:
123
 
            path = self._abspath(relpath)
124
 
            return open(path, 'rb')
125
 
        except (IOError, OSError),e:
126
 
            self._translate_error(e, path)
127
 
 
128
 
    def put(self, relpath, f, mode=None):
129
 
        """Copy the file-like or string object into the location.
130
 
 
131
 
        :param relpath: Location to put the contents, relative to base.
132
 
        :param f:       File-like or string object.
133
 
        """
134
 
        from bzrlib.atomicfile import AtomicFile
135
 
 
136
 
        path = relpath
137
 
        try:
138
 
            path = self._abspath(relpath)
139
 
            check_legal_path(path)
140
 
            fp = AtomicFile(path, 'wb', new_mode=mode)
141
 
        except (IOError, OSError),e:
142
 
            self._translate_error(e, path)
143
 
        try:
144
 
            self._pump(f, fp)
145
 
            fp.commit()
146
 
        finally:
147
 
            fp.close()
148
 
 
149
 
    def iter_files_recursive(self):
150
 
        """Iter the relative paths of files in the transports sub-tree."""
151
 
        queue = list(self.list_dir(u'.'))
152
 
        while queue:
153
 
            relpath = queue.pop(0)
154
 
            st = self.stat(relpath)
155
 
            if S_ISDIR(st[ST_MODE]):
156
 
                for i, basename in enumerate(self.list_dir(relpath)):
157
 
                    queue.insert(i, relpath+'/'+basename)
158
 
            else:
159
 
                yield relpath
160
 
 
161
 
    def mkdir(self, relpath, mode=None):
162
 
        """Create a directory at the given path."""
163
 
        path = relpath
164
 
        try:
165
 
            path = self._abspath(relpath)
166
 
            os.mkdir(path)
167
 
            if mode is not None:
168
 
                os.chmod(path, mode)
169
 
        except (IOError, OSError),e:
170
 
            self._translate_error(e, path)
171
 
 
172
 
    def append(self, relpath, f, mode=None):
173
 
        """Append the text in the file-like object into the final location."""
174
 
        abspath = self._abspath(relpath)
175
 
        try:
176
 
            fp = open(abspath, 'ab')
177
 
            # FIXME should we really be chmodding every time ? RBC 20060523
178
 
            if mode is not None:
179
 
                os.chmod(abspath, mode)
180
 
        except (IOError, OSError),e:
181
 
            self._translate_error(e, relpath)
182
 
        # win32 workaround (tell on an unwritten file returns 0)
183
 
        fp.seek(0, 2)
184
 
        result = fp.tell()
185
 
        self._pump(f, fp)
186
 
        return result
187
 
 
188
 
    def copy(self, rel_from, rel_to):
189
 
        """Copy the item at rel_from to the location at rel_to"""
190
 
        path_from = self._abspath(rel_from)
191
 
        path_to = self._abspath(rel_to)
192
 
        try:
193
 
            shutil.copy(path_from, path_to)
194
 
        except (IOError, OSError),e:
195
 
            # TODO: What about path_to?
196
 
            self._translate_error(e, path_from)
197
 
 
198
 
    def rename(self, rel_from, rel_to):
199
 
        path_from = self._abspath(rel_from)
200
 
        try:
201
 
            # *don't* call bzrlib.osutils.rename, because we want to 
202
 
            # detect errors on rename
203
 
            os.rename(path_from, self._abspath(rel_to))
204
 
        except (IOError, OSError),e:
205
 
            # TODO: What about path_to?
206
 
            self._translate_error(e, path_from)
207
 
 
208
 
    def move(self, rel_from, rel_to):
209
 
        """Move the item at rel_from to the location at rel_to"""
210
 
        path_from = self._abspath(rel_from)
211
 
        path_to = self._abspath(rel_to)
212
 
 
213
 
        try:
214
 
            # this version will delete the destination if necessary
215
 
            rename(path_from, path_to)
216
 
        except (IOError, OSError),e:
217
 
            # TODO: What about path_to?
218
 
            self._translate_error(e, path_from)
219
 
 
220
 
    def delete(self, relpath):
221
 
        """Delete the item at relpath"""
222
 
        path = relpath
223
 
        try:
224
 
            path = self._abspath(relpath)
225
 
            os.remove(path)
226
 
        except (IOError, OSError),e:
227
 
            self._translate_error(e, path)
228
 
 
229
 
    def copy_to(self, relpaths, other, mode=None, pb=None):
230
 
        """Copy a set of entries from self into another Transport.
231
 
 
232
 
        :param relpaths: A list/generator of entries to be copied.
233
 
        """
234
 
        if isinstance(other, LocalTransport):
235
 
            # Both from & to are on the local filesystem
236
 
            # Unfortunately, I can't think of anything faster than just
237
 
            # copying them across, one by one :(
238
 
            total = self._get_total(relpaths)
239
 
            count = 0
240
 
            for path in relpaths:
241
 
                self._update_pb(pb, 'copy-to', count, total)
242
 
                try:
243
 
                    mypath = self._abspath(path)
244
 
                    otherpath = other._abspath(path)
245
 
                    shutil.copy(mypath, otherpath)
246
 
                    if mode is not None:
247
 
                        os.chmod(otherpath, mode)
248
 
                except (IOError, OSError),e:
249
 
                    self._translate_error(e, path)
250
 
                count += 1
251
 
            return count
252
 
        else:
253
 
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
254
 
 
255
 
    def listable(self):
256
 
        """See Transport.listable."""
257
 
        return True
258
 
 
259
 
    def list_dir(self, relpath):
260
 
        """Return a list of all files at the given location.
261
 
        WARNING: many transports do not support this, so trying avoid using
262
 
        it if at all possible.
263
 
        """
264
 
        path = self._abspath(relpath)
265
 
        try:
266
 
            return [urlutils.escape(entry) for entry in os.listdir(path)]
267
 
        except (IOError, OSError), e:
268
 
            self._translate_error(e, path)
269
 
 
270
 
    def stat(self, relpath):
271
 
        """Return the stat information for a file.
272
 
        """
273
 
        path = relpath
274
 
        try:
275
 
            path = self._abspath(relpath)
276
 
            return os.stat(path)
277
 
        except (IOError, OSError),e:
278
 
            self._translate_error(e, path)
279
 
 
280
 
    def lock_read(self, relpath):
281
 
        """Lock the given file for shared (read) access.
282
 
        :return: A lock object, which should be passed to Transport.unlock()
283
 
        """
284
 
        from bzrlib.lock import ReadLock
285
 
        path = relpath
286
 
        try:
287
 
            path = self._abspath(relpath)
288
 
            return ReadLock(path)
289
 
        except (IOError, OSError), e:
290
 
            self._translate_error(e, path)
291
 
 
292
 
    def lock_write(self, relpath):
293
 
        """Lock the given file for exclusive (write) access.
294
 
        WARNING: many transports do not support this, so trying avoid using it
295
 
 
296
 
        :return: A lock object, which should be passed to Transport.unlock()
297
 
        """
298
 
        from bzrlib.lock import WriteLock
299
 
        return WriteLock(self._abspath(relpath))
300
 
 
301
 
    def rmdir(self, relpath):
302
 
        """See Transport.rmdir."""
303
 
        path = relpath
304
 
        try:
305
 
            path = self._abspath(relpath)
306
 
            os.rmdir(path)
307
 
        except (IOError, OSError),e:
308
 
            self._translate_error(e, path)
309
 
 
310
 
    def _can_roundtrip_unix_modebits(self):
311
 
        if sys.platform == 'win32':
312
 
            # anyone else?
313
 
            return False
314
 
        else:
315
 
            return True
316
 
 
317
 
 
318
 
class LocalRelpathServer(Server):
319
 
    """A pretend server for local transports, using relpaths."""
320
 
 
321
 
    def get_url(self):
322
 
        """See Transport.Server.get_url."""
323
 
        return "."
324
 
 
325
 
 
326
 
class LocalAbspathServer(Server):
327
 
    """A pretend server for local transports, using absolute paths."""
328
 
 
329
 
    def get_url(self):
330
 
        """See Transport.Server.get_url."""
331
 
        return os.path.abspath("")
332
 
 
333
 
 
334
 
class LocalURLServer(Server):
335
 
    """A pretend server for local transports, using file:// urls."""
336
 
 
337
 
    def get_url(self):
338
 
        """See Transport.Server.get_url."""
339
 
        return urlutils.local_path_to_url('')
340
 
 
341
 
 
342
 
def get_test_permutations():
343
 
    """Return the permutations to be used in testing."""
344
 
    return [(LocalTransport, LocalRelpathServer),
345
 
            (LocalTransport, LocalAbspathServer),
346
 
            (LocalTransport, LocalURLServer),
347
 
            ]