~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-17 07:52:09 UTC
  • mfrom: (1910.3.4 trivial)
  • Revision ID: pqm@pqm.ubuntu.com-20060817075209-e85a1f9e05ff8b87
(andrew) Trivial fixes to NotImplemented errors.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
"""
21
21
 
22
22
import os
 
23
import shutil
 
24
import sys
23
25
from stat import ST_MODE, S_ISDIR, ST_SIZE, S_IMODE
24
 
import sys
25
 
 
26
 
from bzrlib.lazy_import import lazy_import
27
 
lazy_import(globals(), """
28
 
import errno
29
 
import shutil
 
26
import tempfile
30
27
 
31
28
from bzrlib import (
32
 
    atomicfile,
33
29
    osutils,
34
30
    urlutils,
35
 
    symbol_versioning,
36
 
    transport,
37
31
    )
 
32
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
 
33
                            check_legal_path, rmtree)
 
34
from bzrlib.symbol_versioning import warn
38
35
from bzrlib.trace import mutter
39
 
from bzrlib.transport import LateReadError
40
 
""")
41
 
 
42
36
from bzrlib.transport import Transport, Server
43
37
 
44
38
 
45
39
_append_flags = os.O_CREAT | os.O_APPEND | os.O_WRONLY | osutils.O_BINARY
46
 
_put_non_atomic_flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | osutils.O_BINARY
47
40
 
48
41
 
49
42
class LocalTransport(Transport):
52
45
    def __init__(self, base):
53
46
        """Set the base path where files will be stored."""
54
47
        if not base.startswith('file://'):
55
 
            symbol_versioning.warn(
56
 
                "Instantiating LocalTransport with a filesystem path"
 
48
            warn("Instantiating LocalTransport with a filesystem path"
57
49
                " is deprecated as of bzr 0.8."
58
50
                " Please use bzrlib.transport.get_transport()"
59
51
                " or pass in a file:// url.",
66
58
        super(LocalTransport, self).__init__(base)
67
59
        self._local_base = urlutils.local_path_from_url(base)
68
60
 
 
61
    def should_cache(self):
 
62
        return False
 
63
 
69
64
    def clone(self, offset=None):
70
65
        """Return a new LocalTransport with root at self.base + offset
71
66
        Because the local filesystem does not require a connection, 
74
69
        if offset is None:
75
70
            return LocalTransport(self.base)
76
71
        else:
77
 
            abspath = self.abspath(offset)
78
 
            if abspath == 'file://':
79
 
                # fix upwalk for UNC path
80
 
                # when clone from //HOST/path updir recursively
81
 
                # we should stop at least at //HOST part
82
 
                abspath = self.base
83
 
            return LocalTransport(abspath)
 
72
            return LocalTransport(self.abspath(offset))
84
73
 
85
74
    def _abspath(self, relative_reference):
86
75
        """Return a path for use in os calls.
99
88
        assert isinstance(relpath, basestring), (type(relpath), relpath)
100
89
        # jam 20060426 Using normpath on the real path, because that ensures
101
90
        #       proper handling of stuff like
102
 
        path = osutils.normpath(osutils.pathjoin(
103
 
                    self._local_base, urlutils.unescape(relpath)))
 
91
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
104
92
        return urlutils.local_path_to_url(path)
105
93
 
106
94
    def local_abspath(self, relpath):
125
113
            abspath = u'.'
126
114
 
127
115
        return urlutils.file_relpath(
128
 
            urlutils.strip_trailing_slash(self.base),
 
116
            urlutils.strip_trailing_slash(self.base), 
129
117
            urlutils.strip_trailing_slash(abspath))
130
118
 
131
119
    def has(self, relpath):
136
124
 
137
125
        :param relpath: The relative path to the file
138
126
        """
139
 
        canonical_url = self.abspath(relpath)
140
 
        if canonical_url in transport._file_streams:
141
 
            transport._file_streams[canonical_url].flush()
142
127
        try:
143
128
            path = self._abspath(relpath)
144
129
            return open(path, 'rb')
145
130
        except (IOError, OSError),e:
146
 
            if e.errno == errno.EISDIR:
147
 
                return LateReadError(relpath)
148
 
            self._translate_error(e, path)
149
 
 
150
 
    def put_file(self, relpath, f, mode=None):
151
 
        """Copy the file-like object into the location.
152
 
 
153
 
        :param relpath: Location to put the contents, relative to base.
154
 
        :param f:       File-like object.
155
 
        :param mode: The mode for the newly created file, 
156
 
                     None means just use the default
157
 
        """
158
 
 
159
 
        path = relpath
160
 
        try:
161
 
            path = self._abspath(relpath)
162
 
            osutils.check_legal_path(path)
163
 
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
164
 
        except (IOError, OSError),e:
165
 
            self._translate_error(e, path)
166
 
        try:
167
 
            length = self._pump(f, fp)
168
 
            fp.commit()
169
 
        finally:
170
 
            fp.close()
171
 
        return length
172
 
 
173
 
    def put_bytes(self, relpath, bytes, mode=None):
174
 
        """Copy the string into the location.
175
 
 
176
 
        :param relpath: Location to put the contents, relative to base.
177
 
        :param bytes:   String
178
 
        """
179
 
 
180
 
        path = relpath
181
 
        try:
182
 
            path = self._abspath(relpath)
183
 
            osutils.check_legal_path(path)
184
 
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
185
 
        except (IOError, OSError),e:
186
 
            self._translate_error(e, path)
187
 
        try:
188
 
            fp.write(bytes)
189
 
            fp.commit()
190
 
        finally:
191
 
            fp.close()
192
 
 
193
 
    def _put_non_atomic_helper(self, relpath, writer,
194
 
                               mode=None,
195
 
                               create_parent_dir=False,
196
 
                               dir_mode=None):
197
 
        """Common functionality information for the put_*_non_atomic.
198
 
 
199
 
        This tracks all the create_parent_dir stuff.
200
 
 
201
 
        :param relpath: the path we are putting to.
202
 
        :param writer: A function that takes an os level file descriptor
203
 
            and writes whatever data it needs to write there.
204
 
        :param mode: The final file mode.
205
 
        :param create_parent_dir: Should we be creating the parent directory
206
 
            if it doesn't exist?
207
 
        """
208
 
        abspath = self._abspath(relpath)
209
 
        if mode is None:
210
 
            # os.open() will automatically use the umask
211
 
            local_mode = 0666
212
 
        else:
213
 
            local_mode = mode
214
 
        try:
215
 
            fd = os.open(abspath, _put_non_atomic_flags, local_mode)
216
 
        except (IOError, OSError),e:
217
 
            # We couldn't create the file, maybe we need to create
218
 
            # the parent directory, and try again
219
 
            if (not create_parent_dir
220
 
                or e.errno not in (errno.ENOENT,errno.ENOTDIR)):
221
 
                self._translate_error(e, relpath)
222
 
            parent_dir = os.path.dirname(abspath)
223
 
            if not parent_dir:
224
 
                self._translate_error(e, relpath)
225
 
            self._mkdir(parent_dir, mode=dir_mode)
226
 
            # We created the parent directory, lets try to open the
227
 
            # file again
228
 
            try:
229
 
                fd = os.open(abspath, _put_non_atomic_flags, local_mode)
230
 
            except (IOError, OSError), e:
231
 
                self._translate_error(e, relpath)
232
 
        try:
233
 
            st = os.fstat(fd)
234
 
            if mode is not None and mode != S_IMODE(st.st_mode):
235
 
                # Because of umask, we may still need to chmod the file.
236
 
                # But in the general case, we won't have to
237
 
                os.chmod(abspath, mode)
238
 
            writer(fd)
239
 
        finally:
240
 
            os.close(fd)
241
 
 
242
 
    def put_file_non_atomic(self, relpath, f, mode=None,
243
 
                            create_parent_dir=False,
244
 
                            dir_mode=None):
245
 
        """Copy the file-like object into the target location.
246
 
 
247
 
        This function is not strictly safe to use. It is only meant to
248
 
        be used when you already know that the target does not exist.
249
 
        It is not safe, because it will open and truncate the remote
250
 
        file. So there may be a time when the file has invalid contents.
251
 
 
252
 
        :param relpath: The remote location to put the contents.
253
 
        :param f:       File-like object.
254
 
        :param mode:    Possible access permissions for new file.
255
 
                        None means do not set remote permissions.
256
 
        :param create_parent_dir: If we cannot create the target file because
257
 
                        the parent directory does not exist, go ahead and
258
 
                        create it, and then try again.
259
 
        """
260
 
        def writer(fd):
261
 
            self._pump_to_fd(f, fd)
262
 
        self._put_non_atomic_helper(relpath, writer, mode=mode,
263
 
                                    create_parent_dir=create_parent_dir,
264
 
                                    dir_mode=dir_mode)
265
 
 
266
 
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
267
 
                             create_parent_dir=False, dir_mode=None):
268
 
        def writer(fd):
269
 
            os.write(fd, bytes)
270
 
        self._put_non_atomic_helper(relpath, writer, mode=mode,
271
 
                                    create_parent_dir=create_parent_dir,
272
 
                                    dir_mode=dir_mode)
 
131
            self._translate_error(e, path)
 
132
 
 
133
    def put(self, relpath, f, mode=None):
 
134
        """Copy the file-like or string object into the location.
 
135
 
 
136
        :param relpath: Location to put the contents, relative to base.
 
137
        :param f:       File-like or string object.
 
138
        """
 
139
        from bzrlib.atomicfile import AtomicFile
 
140
 
 
141
        path = relpath
 
142
        try:
 
143
            path = self._abspath(relpath)
 
144
            check_legal_path(path)
 
145
            fp = AtomicFile(path, 'wb', new_mode=mode)
 
146
        except (IOError, OSError),e:
 
147
            self._translate_error(e, path)
 
148
        try:
 
149
            self._pump(f, fp)
 
150
            fp.commit()
 
151
        finally:
 
152
            fp.close()
273
153
 
274
154
    def iter_files_recursive(self):
275
155
        """Iter the relative paths of files in the transports sub-tree."""
283
163
            else:
284
164
                yield relpath
285
165
 
286
 
    def _mkdir(self, abspath, mode=None):
287
 
        """Create a real directory, filtering through mode"""
288
 
        if mode is None:
289
 
            # os.mkdir() will filter through umask
290
 
            local_mode = 0777
291
 
        else:
292
 
            local_mode = mode
 
166
    def mkdir(self, relpath, mode=None):
 
167
        """Create a directory at the given path."""
 
168
        path = relpath
293
169
        try:
294
 
            os.mkdir(abspath, local_mode)
 
170
            if mode is None:
 
171
                # os.mkdir() will filter through umask
 
172
                local_mode = 0777
 
173
            else:
 
174
                local_mode = mode
 
175
            path = self._abspath(relpath)
 
176
            os.mkdir(path, local_mode)
295
177
            if mode is not None:
296
178
                # It is probably faster to just do the chmod, rather than
297
179
                # doing a stat, and then trying to compare
298
 
                os.chmod(abspath, mode)
 
180
                os.chmod(path, mode)
299
181
        except (IOError, OSError),e:
300
 
            self._translate_error(e, abspath)
301
 
 
302
 
    def mkdir(self, relpath, mode=None):
303
 
        """Create a directory at the given path."""
304
 
        self._mkdir(self._abspath(relpath), mode=mode)
305
 
 
306
 
    def open_write_stream(self, relpath, mode=None):
307
 
        """See Transport.open_write_stream."""
308
 
        # initialise the file
309
 
        self.put_bytes_non_atomic(relpath, "", mode=mode)
310
 
        handle = open(self._abspath(relpath), 'wb')
311
 
        transport._file_streams[self.abspath(relpath)] = handle
312
 
        return transport.FileFileStream(self, relpath, handle)
313
 
 
314
 
    def _get_append_file(self, relpath, mode=None):
315
 
        """Call os.open() for the given relpath"""
316
 
        file_abspath = self._abspath(relpath)
 
182
            self._translate_error(e, path)
 
183
 
 
184
    def append(self, relpath, f, mode=None):
 
185
        """Append the text in the file-like object into the final location."""
 
186
        abspath = self._abspath(relpath)
317
187
        if mode is None:
318
188
            # os.open() will automatically use the umask
319
189
            local_mode = 0666
320
190
        else:
321
191
            local_mode = mode
322
192
        try:
323
 
            return file_abspath, os.open(file_abspath, _append_flags, local_mode)
 
193
            fd = os.open(abspath, _append_flags, local_mode)
324
194
        except (IOError, OSError),e:
325
195
            self._translate_error(e, relpath)
326
 
 
327
 
    def _check_mode_and_size(self, file_abspath, fd, mode=None):
328
 
        """Check the mode of the file, and return the current size"""
329
 
        st = os.fstat(fd)
330
 
        if mode is not None and mode != S_IMODE(st.st_mode):
331
 
            # Because of umask, we may still need to chmod the file.
332
 
            # But in the general case, we won't have to
333
 
            os.chmod(file_abspath, mode)
334
 
        return st.st_size
335
 
 
336
 
    def append_file(self, relpath, f, mode=None):
337
 
        """Append the text in the file-like object into the final location."""
338
 
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
339
196
        try:
340
 
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
 
197
            st = os.fstat(fd)
 
198
            result = st.st_size
 
199
            if mode is not None and mode != S_IMODE(st.st_mode):
 
200
                # Because of umask, we may still need to chmod the file.
 
201
                # But in the general case, we won't have to
 
202
                os.chmod(abspath, mode)
341
203
            self._pump_to_fd(f, fd)
342
204
        finally:
343
205
            os.close(fd)
344
206
        return result
345
207
 
346
 
    def append_bytes(self, relpath, bytes, mode=None):
347
 
        """Append the text in the string into the final location."""
348
 
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
349
 
        try:
350
 
            result = self._check_mode_and_size(file_abspath, fd, mode=mode)
351
 
            os.write(fd, bytes)
352
 
        finally:
353
 
            os.close(fd)
354
 
        return result
355
 
 
356
208
    def _pump_to_fd(self, fromfile, to_fd):
357
209
        """Copy contents of one file to another."""
358
210
        BUFSIZE = 32768
389
241
 
390
242
        try:
391
243
            # this version will delete the destination if necessary
392
 
            osutils.rename(path_from, path_to)
 
244
            rename(path_from, path_to)
393
245
        except (IOError, OSError),e:
394
246
            # TODO: What about path_to?
395
247
            self._translate_error(e, path_from)
403
255
        except (IOError, OSError),e:
404
256
            self._translate_error(e, path)
405
257
 
406
 
    def external_url(self):
407
 
        """See bzrlib.transport.Transport.external_url."""
408
 
        # File URL's are externally usable.
409
 
        return self.base
410
 
 
411
258
    def copy_to(self, relpaths, other, mode=None, pb=None):
412
259
        """Copy a set of entries from self into another Transport.
413
260
 
445
292
        """
446
293
        path = self._abspath(relpath)
447
294
        try:
448
 
            entries = os.listdir(path)
 
295
            return [urlutils.escape(entry) for entry in os.listdir(path)]
449
296
        except (IOError, OSError), e:
450
297
            self._translate_error(e, path)
451
 
        return [urlutils.escape(entry) for entry in entries]
452
298
 
453
299
    def stat(self, relpath):
454
300
        """Return the stat information for a file.
498
344
            return True
499
345
 
500
346
 
501
 
class EmulatedWin32LocalTransport(LocalTransport):
502
 
    """Special transport for testing Win32 [UNC] paths on non-windows"""
503
 
 
504
 
    def __init__(self, base):
505
 
        if base[-1] != '/':
506
 
            base = base + '/'
507
 
        super(LocalTransport, self).__init__(base)
508
 
        self._local_base = urlutils._win32_local_path_from_url(base)
509
 
 
510
 
    def abspath(self, relpath):
511
 
        assert isinstance(relpath, basestring), (type(relpath), relpath)
512
 
        path = osutils.normpath(osutils.pathjoin(
513
 
                    self._local_base, urlutils.unescape(relpath)))
514
 
        return urlutils._win32_local_path_to_url(path)
515
 
 
516
 
    def clone(self, offset=None):
517
 
        """Return a new LocalTransport with root at self.base + offset
518
 
        Because the local filesystem does not require a connection, 
519
 
        we can just return a new object.
520
 
        """
521
 
        if offset is None:
522
 
            return EmulatedWin32LocalTransport(self.base)
523
 
        else:
524
 
            abspath = self.abspath(offset)
525
 
            if abspath == 'file://':
526
 
                # fix upwalk for UNC path
527
 
                # when clone from //HOST/path updir recursively
528
 
                # we should stop at least at //HOST part
529
 
                abspath = self.base
530
 
            return EmulatedWin32LocalTransport(abspath)
 
347
class LocalRelpathServer(Server):
 
348
    """A pretend server for local transports, using relpaths."""
 
349
 
 
350
    def get_url(self):
 
351
        """See Transport.Server.get_url."""
 
352
        return "."
 
353
 
 
354
 
 
355
class LocalAbspathServer(Server):
 
356
    """A pretend server for local transports, using absolute paths."""
 
357
 
 
358
    def get_url(self):
 
359
        """See Transport.Server.get_url."""
 
360
        return os.path.abspath("")
531
361
 
532
362
 
533
363
class LocalURLServer(Server):
534
 
    """A pretend server for local transports, using file:// urls.
535
 
    
536
 
    Of course no actual server is required to access the local filesystem, so
537
 
    this just exists to tell the test code how to get to it.
538
 
    """
539
 
 
540
 
    def setUp(self):
541
 
        """Setup the server to service requests.
542
 
        
543
 
        :param decorated_transport: ignored by this implementation.
544
 
        """
 
364
    """A pretend server for local transports, using file:// urls."""
545
365
 
546
366
    def get_url(self):
547
367
        """See Transport.Server.get_url."""
550
370
 
551
371
def get_test_permutations():
552
372
    """Return the permutations to be used in testing."""
553
 
    return [
 
373
    return [(LocalTransport, LocalRelpathServer),
 
374
            (LocalTransport, LocalAbspathServer),
554
375
            (LocalTransport, LocalURLServer),
555
376
            ]