~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

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
 
23
import shutil
24
24
import sys
25
 
 
26
 
from bzrlib.lazy_import import lazy_import
27
 
lazy_import(globals(), """
28
 
import errno
29
 
import shutil
30
 
 
31
 
from bzrlib import (
32
 
    atomicfile,
33
 
    osutils,
34
 
    urlutils,
35
 
    symbol_versioning,
36
 
    )
 
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
37
31
from bzrlib.trace import mutter
38
 
""")
39
 
 
40
32
from bzrlib.transport import Transport, Server
41
 
 
42
 
 
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
 
33
import bzrlib.urlutils as urlutils
45
34
 
46
35
 
47
36
class LocalTransport(Transport):
50
39
    def __init__(self, base):
51
40
        """Set the base path where files will be stored."""
52
41
        if not base.startswith('file://'):
53
 
            symbol_versioning.warn(
54
 
                "Instantiating LocalTransport with a filesystem path"
 
42
            warn("Instantiating LocalTransport with a filesystem path"
55
43
                " is deprecated as of bzr 0.8."
56
44
                " Please use bzrlib.transport.get_transport()"
57
45
                " or pass in a file:// url.",
63
51
            base = base + '/'
64
52
        super(LocalTransport, self).__init__(base)
65
53
        self._local_base = urlutils.local_path_from_url(base)
 
54
        ## mutter("_local_base: %r => %r", base, self._local_base)
66
55
 
67
56
    def should_cache(self):
68
57
        return False
75
64
        if offset is None:
76
65
            return LocalTransport(self.base)
77
66
        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)
 
67
            return LocalTransport(self.abspath(offset))
85
68
 
86
69
    def _abspath(self, relative_reference):
87
70
        """Return a path for use in os calls.
100
83
        assert isinstance(relpath, basestring), (type(relpath), relpath)
101
84
        # jam 20060426 Using normpath on the real path, because that ensures
102
85
        #       proper handling of stuff like
103
 
        path = osutils.normpath(osutils.pathjoin(
104
 
                    self._local_base, urlutils.unescape(relpath)))
 
86
        path = normpath(pathjoin(self._local_base, urlutils.unescape(relpath)))
105
87
        return urlutils.local_path_to_url(path)
106
88
 
107
89
    def local_abspath(self, relpath):
143
125
        except (IOError, OSError),e:
144
126
            self._translate_error(e, path)
145
127
 
146
 
    def put_file(self, relpath, f, mode=None):
147
 
        """Copy the file-like object into the location.
 
128
    def put(self, relpath, f, mode=None):
 
129
        """Copy the file-like or string object into the location.
148
130
 
149
131
        :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
 
132
        :param f:       File-like or string object.
153
133
        """
 
134
        from bzrlib.atomicfile import AtomicFile
154
135
 
155
136
        path = relpath
156
137
        try:
157
138
            path = self._abspath(relpath)
158
 
            osutils.check_legal_path(path)
159
 
            fp = atomicfile.AtomicFile(path, 'wb', new_mode=mode)
 
139
            check_legal_path(path)
 
140
            fp = AtomicFile(path, 'wb', new_mode=mode)
160
141
        except (IOError, OSError),e:
161
142
            self._translate_error(e, path)
162
143
        try:
165
146
        finally:
166
147
            fp.close()
167
148
 
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
149
    def iter_files_recursive(self):
270
150
        """Iter the relative paths of files in the transports sub-tree."""
271
151
        queue = list(self.list_dir(u'.'))
278
158
            else:
279
159
                yield relpath
280
160
 
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
288
 
        try:
289
 
            os.mkdir(abspath, local_mode)
290
 
            if mode is not None:
291
 
                # It is probably faster to just do the chmod, rather than
292
 
                # doing a stat, and then trying to compare
293
 
                os.chmod(abspath, mode)
294
 
        except (IOError, OSError),e:
295
 
            self._translate_error(e, abspath)
296
 
 
297
161
    def mkdir(self, relpath, mode=None):
298
162
        """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
 
163
        path = relpath
309
164
        try:
310
 
            return file_abspath, os.open(file_abspath, _append_flags, local_mode)
 
165
            path = self._abspath(relpath)
 
166
            os.mkdir(path)
 
167
            if mode is not None:
 
168
                os.chmod(path, mode)
311
169
        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):
 
170
            self._translate_error(e, path)
 
171
 
 
172
    def append(self, relpath, f, mode=None):
324
173
        """Append the text in the file-like object into the final location."""
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)
328
 
            self._pump_to_fd(f, fd)
329
 
        finally:
330
 
            os.close(fd)
331
 
        return result
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
 
 
343
 
    def _pump_to_fd(self, fromfile, to_fd):
344
 
        """Copy contents of one file to another."""
345
 
        BUFSIZE = 32768
346
 
        while True:
347
 
            b = fromfile.read(BUFSIZE)
348
 
            if not b:
349
 
                break
350
 
            os.write(to_fd, b)
 
174
        abspath = self._abspath(relpath)
 
175
        fp = None
 
176
        try:
 
177
            try:
 
178
                fp = open(abspath, 'ab')
 
179
                # FIXME should we really be chmodding every time ? RBC 20060523
 
180
                if mode is not None:
 
181
                    os.chmod(abspath, mode)
 
182
            except (IOError, OSError),e:
 
183
                self._translate_error(e, relpath)
 
184
            # win32 workaround (tell on an unwritten file returns 0)
 
185
            fp.seek(0, 2)
 
186
            result = fp.tell()
 
187
            self._pump(f, fp)
 
188
        finally:
 
189
            if fp is not None:
 
190
                fp.close()
 
191
        return result
351
192
 
352
193
    def copy(self, rel_from, rel_to):
353
194
        """Copy the item at rel_from to the location at rel_to"""
376
217
 
377
218
        try:
378
219
            # this version will delete the destination if necessary
379
 
            osutils.rename(path_from, path_to)
 
220
            rename(path_from, path_to)
380
221
        except (IOError, OSError),e:
381
222
            # TODO: What about path_to?
382
223
            self._translate_error(e, path_from)
427
268
        """
428
269
        path = self._abspath(relpath)
429
270
        try:
430
 
            entries = os.listdir(path)
 
271
            return [urlutils.escape(entry) for entry in os.listdir(path)]
431
272
        except (IOError, OSError), e:
432
273
            self._translate_error(e, path)
433
 
        return [urlutils.escape(entry) for entry in entries]
434
274
 
435
275
    def stat(self, relpath):
436
276
        """Return the stat information for a file.
480
320
            return True
481
321
 
482
322
 
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)
 
323
class LocalRelpathServer(Server):
 
324
    """A pretend server for local transports, using relpaths."""
 
325
 
 
326
    def get_url(self):
 
327
        """See Transport.Server.get_url."""
 
328
        return "."
 
329
 
 
330
 
 
331
class LocalAbspathServer(Server):
 
332
    """A pretend server for local transports, using absolute paths."""
 
333
 
 
334
    def get_url(self):
 
335
        """See Transport.Server.get_url."""
 
336
        return os.path.abspath("")
513
337
 
514
338
 
515
339
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
 
    """
521
 
 
522
 
    def setUp(self):
523
 
        """Setup the server to service requests.
524
 
        
525
 
        :param decorated_transport: ignored by this implementation.
526
 
        """
 
340
    """A pretend server for local transports, using file:// urls."""
527
341
 
528
342
    def get_url(self):
529
343
        """See Transport.Server.get_url."""
532
346
 
533
347
def get_test_permutations():
534
348
    """Return the permutations to be used in testing."""
535
 
    return [
 
349
    return [(LocalTransport, LocalRelpathServer),
 
350
            (LocalTransport, LocalAbspathServer),
536
351
            (LocalTransport, LocalURLServer),
537
352
            ]