~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Aaron Bentley
  • Date: 2007-06-11 14:59:52 UTC
  • mto: (2520.5.2 bzr.mpbundle)
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: abentley@panoramicfeedback.com-20070611145952-cwt4480gphdhen6l
Get installation started

Show diffs side-by-side

added added

removed removed

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