/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-09-06 16:21:16 UTC
  • mfrom: (1955.3.30 transport_bytes)
  • Revision ID: pqm@pqm.ubuntu.com-20060906162116-90b02cf97bcc11e8
(jam) create Transport.*_{file,bytes}

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 errno
 
23
import os
 
24
import shutil
 
25
import sys
 
26
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
 
27
import tempfile
 
28
 
 
29
from bzrlib import (
 
30
    atomicfile,
 
31
    osutils,
 
32
    urlutils,
 
33
    )
 
34
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
 
35
                            check_legal_path, rmtree)
 
36
from bzrlib.symbol_versioning import warn
 
37
from bzrlib.trace import mutter
 
38
from bzrlib.transport import Transport, Server
 
39
 
 
40
 
 
41
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
 
42
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
 
43
 
 
44
 
 
45
class LocalTransport(Transport):
 
46
    """This is the transport agent for local filesystem access."""
 
47
 
 
48
    def __init__(self, base):
 
49
        """Set the base path where files will be stored."""
 
50
        if not base.startswith('file://'):
 
51
            warn("Instantiating LocalTransport with a filesystem path"
 
52
                " is deprecated as of bzr 0.8."
 
53
                " Please use bzrlib.transport.get_transport()"
 
54
                " or pass in a file:// url.",
 
55
                 DeprecationWarning,
 
56
                 stacklevel=2
 
57
                 )
 
58
            base = urlutils.local_path_to_url(base)
 
59
        if base[-1] != '/':
 
60
            base = base + '/'
 
61
        super(LocalTransport, self).__init__(base)
 
62
        self._local_base = urlutils.local_path_from_url(base)
 
63
 
 
64
    def should_cache(self):
 
65
        return False
 
66
 
 
67
    def clone(self, offset=None):
 
68
        """Return a new LocalTransport with root at self.base + offset
 
69
        Because the local filesystem does not require a connection, 
 
70
        we can just return a new object.
 
71
        """
 
72
        if offset is None:
 
73
            return LocalTransport(self.base)
 
74
        else:
 
75
            return LocalTransport(self.abspath(offset))
 
76
 
 
77
    def _abspath(self, relative_reference):
 
78
        """Return a path for use in os calls.
 
79
 
 
80
        Several assumptions are made:
 
81
         - relative_reference does not contain '..'
 
82
         - relative_reference is url escaped.
 
83
        """
 
84
        if relative_reference in ('.', ''):
 
85
            return self._local_base
 
86
        return self._local_base + urlutils.unescape(relative_reference)
 
87
 
 
88
    def abspath(self, relpath):
 
89
        """Return the full url to the given relative URL."""
 
90
        # TODO: url escape the result. RBC 20060523.
 
91
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
92
        # jam 20060426 Using normpath on the real path, because that ensures
 
93
        #       proper handling of stuff like
 
94
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
 
95
        return urlutils.local_path_to_url(path)
 
96
 
 
97
    def local_abspath(self, relpath):
 
98
        """Transform the given relative path URL into the actual path on disk
 
99
 
 
100
        This function only exists for the LocalTransport, since it is
 
101
        the only one that has direct local access.
 
102
        This is mostly for stuff like WorkingTree which needs to know
 
103
        the local working directory.
 
104
        
 
105
        This function is quite expensive: it calls realpath which resolves
 
106
        symlinks.
 
107
        """
 
108
        absurl = self.abspath(relpath)
 
109
        # mutter(u'relpath %s => base: %s, absurl %s', relpath, self.base, absurl)
 
110
        return urlutils.local_path_from_url(absurl)
 
111
 
 
112
    def relpath(self, abspath):
 
113
        """Return the local path portion from a given absolute path.
 
114
        """
 
115
        if abspath is None:
 
116
            abspath = u'.'
 
117
 
 
118
        return urlutils.file_relpath(
 
119
            urlutils.strip_trailing_slash(self.base), 
 
120
            urlutils.strip_trailing_slash(abspath))
 
121
 
 
122
    def has(self, relpath):
 
123
        return os.access(self._abspath(relpath), os.F_OK)
 
124
 
 
125
    def get(self, relpath):
 
126
        """Get the file at the given relative path.
 
127
 
 
128
        :param relpath: The relative path to the file
 
129
        """
 
130
        try:
 
131
            path = self._abspath(relpath)
 
132
            return open(path, 'rb')
 
133
        except (IOError, OSError),e:
 
134
            self._translate_error(e, path)
 
135
 
 
136
    def put_file(self, relpath, f, mode=None):
 
137
        """Copy the file-like object into the location.
 
138
 
 
139
        :param relpath: Location to put the contents, relative to base.
 
140
        :param f:       File-like object.
 
141
        :param mode: The mode for the newly created file, 
 
142
                     None means just use the default
 
143
        """
 
144
 
 
145
        path = relpath
 
146
        try:
 
147
            path = self._abspath(relpath)
 
148
            check_legal_path(path)
 
149
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
 
150
        except (IOError, OSError),e:
 
151
            self._translate_error(e, path)
 
152
        try:
 
153
            self._pump(f, fp)
 
154
            fp.commit()
 
155
        finally:
 
156
            fp.close()
 
157
 
 
158
    def put_bytes(self, relpath, bytes, mode=None):
 
159
        """Copy the string into the location.
 
160
 
 
161
        :param relpath: Location to put the contents, relative to base.
 
162
        :param bytes:   String
 
163
        """
 
164
 
 
165
        path = relpath
 
166
        try:
 
167
            path = self._abspath(relpath)
 
168
            check_legal_path(path)
 
169
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
 
170
        except (IOError, OSError),e:
 
171
            self._translate_error(e, path)
 
172
        try:
 
173
            fp.write(bytes)
 
174
            fp.commit()
 
175
        finally:
 
176
            fp.close()
 
177
 
 
178
    def _put_non_atomic_helper(self, relpath, writer,
 
179
                               mode=None,
 
180
                               create_parent_dir=False):
 
181
        """Common functionality information for the put_*_non_atomic.
 
182
 
 
183
        This tracks all the create_parent_dir stuff.
 
184
 
 
185
        :param relpath: the path we are putting to.
 
186
        :param writer: A function that takes an os level file descriptor
 
187
            and writes whatever data it needs to write there.
 
188
        :param mode: The final file mode.
 
189
        :param create_parent_dir: Should we be creating the parent directory
 
190
            if it doesn't exist?
 
191
        """
 
192
        abspath = self._abspath(relpath)
 
193
        if mode is None:
 
194
            # os.open() will automatically use the umask
 
195
            local_mode = 0666
 
196
        else:
 
197
            local_mode = mode
 
198
        try:
 
199
            fd = os.open(abspath, _put_non_atomic_flags, local_mode)
 
200
        except (IOError, OSError),e:
 
201
            # We couldn't create the file, maybe we need to create
 
202
            # the parent directory, and try again
 
203
            if (not create_parent_dir
 
204
                or e.errno not in (errno.ENOENT,errno.ENOTDIR)):
 
205
                self._translate_error(e, relpath)
 
206
            parent_dir = os.path.dirname(abspath)
 
207
            if not parent_dir:
 
208
                self._translate_error(e, relpath)
 
209
            try:
 
210
                os.mkdir(parent_dir)
 
211
            except (IOError, OSError), e:
 
212
                self._translate_error(e, relpath)
 
213
            # We created the parent directory, lets try to open the
 
214
            # file again
 
215
            try:
 
216
                fd = os.open(abspath, _put_non_atomic_flags, local_mode)
 
217
            except (IOError, OSError), e:
 
218
                self._translate_error(e, relpath)
 
219
        try:
 
220
            st = os.fstat(fd)
 
221
            if mode is not None and mode != S_IMODE(st.st_mode):
 
222
                # Because of umask, we may still need to chmod the file.
 
223
                # But in the general case, we won't have to
 
224
                os.chmod(abspath, mode)
 
225
            writer(fd)
 
226
        finally:
 
227
            os.close(fd)
 
228
 
 
229
    def put_file_non_atomic(self, relpath, f, mode=None,
 
230
                            create_parent_dir=False):
 
231
        """Copy the file-like object into the target location.
 
232
 
 
233
        This function is not strictly safe to use. It is only meant to
 
234
        be used when you already know that the target does not exist.
 
235
        It is not safe, because it will open and truncate the remote
 
236
        file. So there may be a time when the file has invalid contents.
 
237
 
 
238
        :param relpath: The remote location to put the contents.
 
239
        :param f:       File-like object.
 
240
        :param mode:    Possible access permissions for new file.
 
241
                        None means do not set remote permissions.
 
242
        :param create_parent_dir: If we cannot create the target file because
 
243
                        the parent directory does not exist, go ahead and
 
244
                        create it, and then try again.
 
245
        """
 
246
        def writer(fd):
 
247
            self._pump_to_fd(f, fd)
 
248
        self._put_non_atomic_helper(relpath, writer, mode=mode,
 
249
                                    create_parent_dir=create_parent_dir)
 
250
 
 
251
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
 
252
                             create_parent_dir=False):
 
253
        def writer(fd):
 
254
            os.write(fd, bytes)
 
255
        self._put_non_atomic_helper(relpath, writer, mode=mode,
 
256
                                    create_parent_dir=create_parent_dir)
 
257
 
 
258
    def iter_files_recursive(self):
 
259
        """Iter the relative paths of files in the transports sub-tree."""
 
260
        queue = list(self.list_dir(u'.'))
 
261
        while queue:
 
262
            relpath = queue.pop(0)
 
263
            st = self.stat(relpath)
 
264
            if S_ISDIR(st[ST_MODE]):
 
265
                for i, basename in enumerate(self.list_dir(relpath)):
 
266
                    queue.insert(i, relpath+'/'+basename)
 
267
            else:
 
268
                yield relpath
 
269
 
 
270
    def mkdir(self, relpath, mode=None):
 
271
        """Create a directory at the given path."""
 
272
        path = relpath
 
273
        try:
 
274
            if mode is None:
 
275
                # os.mkdir() will filter through umask
 
276
                local_mode = 0777
 
277
            else:
 
278
                local_mode = mode
 
279
            path = self._abspath(relpath)
 
280
            os.mkdir(path, local_mode)
 
281
            if mode is not None:
 
282
                # It is probably faster to just do the chmod, rather than
 
283
                # doing a stat, and then trying to compare
 
284
                os.chmod(path, mode)
 
285
        except (IOError, OSError),e:
 
286
            self._translate_error(e, path)
 
287
 
 
288
    def _get_append_file(self, relpath, mode=None):
 
289
        """Call os.open() for the given relpath"""
 
290
        file_abspath = self._abspath(relpath)
 
291
        if mode is None:
 
292
            # os.open() will automatically use the umask
 
293
            local_mode = 0666
 
294
        else:
 
295
            local_mode = mode
 
296
        try:
 
297
            return file_abspath, os.open(file_abspath, _append_flags, local_mode)
 
298
        except (IOError, OSError),e:
 
299
            self._translate_error(e, relpath)
 
300
 
 
301
    def _check_mode_and_size(self, file_abspath, fd, mode=None):
 
302
        """Check the mode of the file, and return the current size"""
 
303
        st = os.fstat(fd)
 
304
        if mode is not None and mode != S_IMODE(st.st_mode):
 
305
            # Because of umask, we may still need to chmod the file.
 
306
            # But in the general case, we won't have to
 
307
            os.chmod(file_abspath, mode)
 
308
        return st.st_size
 
309
 
 
310
    def append_file(self, relpath, f, mode=None):
 
311
        """Append the text in the file-like object into the final location."""
 
312
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
 
313
        try:
 
314
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
 
315
            self._pump_to_fd(f, fd)
 
316
        finally:
 
317
            os.close(fd)
 
318
        return result
 
319
 
 
320
    def append_bytes(self, relpath, bytes, mode=None):
 
321
        """Append the text in the string into the final location."""
 
322
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
 
323
        try:
 
324
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
 
325
            os.write(fd, bytes)
 
326
        finally:
 
327
            os.close(fd)
 
328
        return result
 
329
 
 
330
    def _pump_to_fd(self, fromfile, to_fd):
 
331
        """Copy contents of one file to another."""
 
332
        BUFSIZE = 32768
 
333
        while True:
 
334
            b = fromfile.read(BUFSIZE)
 
335
            if not b:
 
336
                break
 
337
            os.write(to_fd, b)
 
338
 
 
339
    def copy(self, rel_from, rel_to):
 
340
        """Copy the item at rel_from to the location at rel_to"""
 
341
        path_from = self._abspath(rel_from)
 
342
        path_to = self._abspath(rel_to)
 
343
        try:
 
344
            shutil.copy(path_from, path_to)
 
345
        except (IOError, OSError),e:
 
346
            # TODO: What about path_to?
 
347
            self._translate_error(e, path_from)
 
348
 
 
349
    def rename(self, rel_from, rel_to):
 
350
        path_from = self._abspath(rel_from)
 
351
        try:
 
352
            # *don't* call bzrlib.osutils.rename, because we want to 
 
353
            # detect errors on rename
 
354
            os.rename(path_from, self._abspath(rel_to))
 
355
        except (IOError, OSError),e:
 
356
            # TODO: What about path_to?
 
357
            self._translate_error(e, path_from)
 
358
 
 
359
    def move(self, rel_from, rel_to):
 
360
        """Move the item at rel_from to the location at rel_to"""
 
361
        path_from = self._abspath(rel_from)
 
362
        path_to = self._abspath(rel_to)
 
363
 
 
364
        try:
 
365
            # this version will delete the destination if necessary
 
366
            rename(path_from, path_to)
 
367
        except (IOError, OSError),e:
 
368
            # TODO: What about path_to?
 
369
            self._translate_error(e, path_from)
 
370
 
 
371
    def delete(self, relpath):
 
372
        """Delete the item at relpath"""
 
373
        path = relpath
 
374
        try:
 
375
            path = self._abspath(relpath)
 
376
            os.remove(path)
 
377
        except (IOError, OSError),e:
 
378
            self._translate_error(e, path)
 
379
 
 
380
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
381
        """Copy a set of entries from self into another Transport.
 
382
 
 
383
        :param relpaths: A list/generator of entries to be copied.
 
384
        """
 
385
        if isinstance(other, LocalTransport):
 
386
            # Both from & to are on the local filesystem
 
387
            # Unfortunately, I can't think of anything faster than just
 
388
            # copying them across, one by one :(
 
389
            total = self._get_total(relpaths)
 
390
            count = 0
 
391
            for path in relpaths:
 
392
                self._update_pb(pb, 'copy-to', count, total)
 
393
                try:
 
394
                    mypath = self._abspath(path)
 
395
                    otherpath = other._abspath(path)
 
396
                    shutil.copy(mypath, otherpath)
 
397
                    if mode is not None:
 
398
                        os.chmod(otherpath, mode)
 
399
                except (IOError, OSError),e:
 
400
                    self._translate_error(e, path)
 
401
                count += 1
 
402
            return count
 
403
        else:
 
404
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
405
 
 
406
    def listable(self):
 
407
        """See Transport.listable."""
 
408
        return True
 
409
 
 
410
    def list_dir(self, relpath):
 
411
        """Return a list of all files at the given location.
 
412
        WARNING: many transports do not support this, so trying avoid using
 
413
        it if at all possible.
 
414
        """
 
415
        path = self._abspath(relpath)
 
416
        try:
 
417
            entries = os.listdir(path)
 
418
        except (IOError, OSError), e:
 
419
            self._translate_error(e, path)
 
420
        return [urlutils.escape(entry) for entry in entries]
 
421
 
 
422
    def stat(self, relpath):
 
423
        """Return the stat information for a file.
 
424
        """
 
425
        path = relpath
 
426
        try:
 
427
            path = self._abspath(relpath)
 
428
            return os.stat(path)
 
429
        except (IOError, OSError),e:
 
430
            self._translate_error(e, path)
 
431
 
 
432
    def lock_read(self, relpath):
 
433
        """Lock the given file for shared (read) access.
 
434
        :return: A lock object, which should be passed to Transport.unlock()
 
435
        """
 
436
        from bzrlib.lock import ReadLock
 
437
        path = relpath
 
438
        try:
 
439
            path = self._abspath(relpath)
 
440
            return ReadLock(path)
 
441
        except (IOError, OSError), e:
 
442
            self._translate_error(e, path)
 
443
 
 
444
    def lock_write(self, relpath):
 
445
        """Lock the given file for exclusive (write) access.
 
446
        WARNING: many transports do not support this, so trying avoid using it
 
447
 
 
448
        :return: A lock object, which should be passed to Transport.unlock()
 
449
        """
 
450
        from bzrlib.lock import WriteLock
 
451
        return WriteLock(self._abspath(relpath))
 
452
 
 
453
    def rmdir(self, relpath):
 
454
        """See Transport.rmdir."""
 
455
        path = relpath
 
456
        try:
 
457
            path = self._abspath(relpath)
 
458
            os.rmdir(path)
 
459
        except (IOError, OSError),e:
 
460
            self._translate_error(e, path)
 
461
 
 
462
    def _can_roundtrip_unix_modebits(self):
 
463
        if sys.platform == 'win32':
 
464
            # anyone else?
 
465
            return False
 
466
        else:
 
467
            return True
 
468
 
 
469
 
 
470
class LocalRelpathServer(Server):
 
471
    """A pretend server for local transports, using relpaths."""
 
472
 
 
473
    def get_url(self):
 
474
        """See Transport.Server.get_url."""
 
475
        return "."
 
476
 
 
477
 
 
478
class LocalAbspathServer(Server):
 
479
    """A pretend server for local transports, using absolute paths."""
 
480
 
 
481
    def get_url(self):
 
482
        """See Transport.Server.get_url."""
 
483
        return os.path.abspath("")
 
484
 
 
485
 
 
486
class LocalURLServer(Server):
 
487
    """A pretend server for local transports, using file:// urls."""
 
488
 
 
489
    def get_url(self):
 
490
        """See Transport.Server.get_url."""
 
491
        return urlutils.local_path_to_url('')
 
492
 
 
493
 
 
494
def get_test_permutations():
 
495
    """Return the permutations to be used in testing."""
 
496
    return [(LocalTransport, LocalRelpathServer),
 
497
            (LocalTransport, LocalAbspathServer),
 
498
            (LocalTransport, LocalURLServer),
 
499
            ]