~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Alexander Belchenko
  • Date: 2006-09-05 07:37:01 UTC
  • mto: (1711.9.17 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1984.
  • Revision ID: bialix@ukr.net-20060905073701-93e7c0a44dd7ee05
small but important fix for 'make installer' dependencies

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
31
    )
 
32
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
 
33
                            check_legal_path, rmtree)
 
34
from bzrlib.symbol_versioning import warn
37
35
from bzrlib.trace import mutter
38
 
""")
39
 
 
40
36
from bzrlib.transport import Transport, Server
41
37
 
42
38
 
43
39
_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
45
40
 
46
41
 
47
42
class LocalTransport(Transport):
50
45
    def __init__(self, base):
51
46
        """Set the base path where files will be stored."""
52
47
        if not base.startswith('file://'):
53
 
            symbol_versioning.warn(
54
 
                "Instantiating LocalTransport with a filesystem path"
 
48
            warn("Instantiating LocalTransport with a filesystem path"
55
49
                " is deprecated as of bzr 0.8."
56
50
                " Please use bzrlib.transport.get_transport()"
57
51
                " or pass in a file:// url.",
75
69
        if offset is None:
76
70
            return LocalTransport(self.base)
77
71
        else:
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)
 
72
            return LocalTransport(self.abspath(offset))
85
73
 
86
74
    def _abspath(self, relative_reference):
87
75
        """Return a path for use in os calls.
100
88
        assert isinstance(relpath, basestring), (type(relpath), relpath)
101
89
        # jam 20060426 Using normpath on the real path, because that ensures
102
90
        #       proper handling of stuff like
103
 
        path = osutils.normpath(osutils.pathjoin(
104
 
                    self._local_base, urlutils.unescape(relpath)))
 
91
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
105
92
        return urlutils.local_path_to_url(path)
106
93
 
107
94
    def local_abspath(self, relpath):
143
130
        except (IOError, OSError),e:
144
131
            self._translate_error(e, path)
145
132
 
146
 
    def put_file(self, relpath, f, mode=None):
147
 
        """Copy the file-like object into the location.
 
133
    def put(self, relpath, f, mode=None):
 
134
        """Copy the file-like or string object into the location.
148
135
 
149
136
        :param relpath: Location to put the contents, relative to base.
150
 
        :param f:       File-like object.
151
 
        :param mode: The mode for the newly created file, 
152
 
                     None means just use the default
 
137
        :param f:       File-like or string object.
153
138
        """
 
139
        from bzrlib.atomicfile import AtomicFile
154
140
 
155
141
        path = relpath
156
142
        try:
157
143
            path = self._abspath(relpath)
158
 
            osutils.check_legal_path(path)
159
 
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
 
144
            check_legal_path(path)
 
145
            fp = AtomicFile(path, 'wb', new_mode=mode)
160
146
        except (IOError, OSError),e:
161
147
            self._translate_error(e, path)
162
148
        try:
165
151
        finally:
166
152
            fp.close()
167
153
 
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
 
 
269
154
    def iter_files_recursive(self):
270
155
        """Iter the relative paths of files in the transports sub-tree."""
271
156
        queue = list(self.list_dir(u'.'))
278
163
            else:
279
164
                yield relpath
280
165
 
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
 
166
    def mkdir(self, relpath, mode=None):
 
167
        """Create a directory at the given path."""
 
168
        path = relpath
288
169
        try:
289
 
            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)
290
177
            if mode is not None:
291
178
                # It is probably faster to just do the chmod, rather than
292
179
                # doing a stat, and then trying to compare
293
 
                os.chmod(abspath, mode)
 
180
                os.chmod(path, mode)
294
181
        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)
 
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)
304
187
        if mode is None:
305
188
            # os.open() will automatically use the umask
306
189
            local_mode = 0666
307
190
        else:
308
191
            local_mode = mode
309
192
        try:
310
 
            return file_abspath, os.open(file_abspath, _append_flags, local_mode)
 
193
            fd = os.open(abspath, _append_flags, local_mode)
311
194
        except (IOError, OSError),e:
312
195
            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):
324
 
        """Append the text in the file-like object into the final location."""
325
 
        file_abspath, fd = self._get_append_file(relpath, mode=mode)
326
196
        try:
327
 
            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)
328
203
            self._pump_to_fd(f, fd)
329
204
        finally:
330
205
            os.close(fd)
331
206
        return result
332
207
 
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
 
 
343
208
    def _pump_to_fd(self, fromfile, to_fd):
344
209
        """Copy contents of one file to another."""
345
210
        BUFSIZE = 32768
376
241
 
377
242
        try:
378
243
            # this version will delete the destination if necessary
379
 
            osutils.rename(path_from, path_to)
 
244
            rename(path_from, path_to)
380
245
        except (IOError, OSError),e:
381
246
            # TODO: What about path_to?
382
247
            self._translate_error(e, path_from)
480
345
            return True
481
346
 
482
347
 
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)
 
348
class LocalRelpathServer(Server):
 
349
    """A pretend server for local transports, using relpaths."""
 
350
 
 
351
    def get_url(self):
 
352
        """See Transport.Server.get_url."""
 
353
        return "."
 
354
 
 
355
 
 
356
class LocalAbspathServer(Server):
 
357
    """A pretend server for local transports, using absolute paths."""
 
358
 
 
359
    def get_url(self):
 
360
        """See Transport.Server.get_url."""
 
361
        return os.path.abspath("")
513
362
 
514
363
 
515
364
class LocalURLServer(Server):
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
 
    """
 
365
    """A pretend server for local transports, using file:// urls."""
521
366
 
522
367
    def get_url(self):
523
368
        """See Transport.Server.get_url."""
526
371
 
527
372
def get_test_permutations():
528
373
    """Return the permutations to be used in testing."""
529
 
    return [
 
374
    return [(LocalTransport, LocalRelpathServer),
 
375
            (LocalTransport, LocalAbspathServer),
530
376
            (LocalTransport, LocalURLServer),
531
377
            ]