~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Robert Collins
  • Date: 2006-05-24 08:14:45 UTC
  • mfrom: (1725.1.1 benchmark)
  • mto: (1725.2.6 commit)
  • mto: This revision was merged to the branch mainline in revision 1729.
  • Revision ID: robertc@robertcollins.net-20060524081445-c046b4406ffc8dfa
(rbc)Merge in benchmark --lsprof-timed lsprofiling feature. (Robert Collins, Martin Pool).

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
import os
 
22
import shutil
 
23
import sys
 
24
from stat import ST_MODE, S_ISDIR, ST_SIZE
 
25
import tempfile
 
26
import urllib
 
27
 
 
28
from bzrlib.trace import mutter
 
29
from bzrlib.transport import Transport, Server
 
30
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename, 
 
31
                            check_legal_path, rmtree)
 
32
 
 
33
 
 
34
class LocalTransport(Transport):
 
35
    """This is the transport agent for local filesystem access."""
 
36
 
 
37
    def __init__(self, base):
 
38
        """Set the base path where files will be stored."""
 
39
        if base.startswith('file://'):
 
40
            base = base[len('file://'):]
 
41
        # realpath is incompatible with symlinks. When we traverse
 
42
        # up we might be able to normpath stuff. RBC 20051003
 
43
        base = normpath(abspath(base))
 
44
        if base[-1] != '/':
 
45
            base = base + '/'
 
46
        super(LocalTransport, self).__init__(base)
 
47
 
 
48
    def should_cache(self):
 
49
        return False
 
50
 
 
51
    def clone(self, offset=None):
 
52
        """Return a new LocalTransport with root at self.base + offset
 
53
        Because the local filesystem does not require a connection, 
 
54
        we can just return a new object.
 
55
        """
 
56
        if offset is None:
 
57
            return LocalTransport(self.base)
 
58
        else:
 
59
            return LocalTransport(self.abspath(offset))
 
60
 
 
61
    def abspath(self, relpath):
 
62
        """Return the full url to the given relative URL."""
 
63
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
64
        result = normpath(pathjoin(self.base, urllib.unquote(relpath)))
 
65
        #if result[-1] != '/':
 
66
        #    result += '/'
 
67
        return result
 
68
 
 
69
    def relpath(self, abspath):
 
70
        """Return the local path portion from a given absolute path.
 
71
        """
 
72
        from bzrlib.osutils import relpath, strip_trailing_slash
 
73
        if abspath is None:
 
74
            abspath = u'.'
 
75
 
 
76
        return relpath(strip_trailing_slash(self.base), 
 
77
                       strip_trailing_slash(abspath))
 
78
 
 
79
    def has(self, relpath):
 
80
        return os.access(self.abspath(relpath), os.F_OK)
 
81
 
 
82
    def get(self, relpath):
 
83
        """Get the file at the given relative path.
 
84
 
 
85
        :param relpath: The relative path to the file
 
86
        """
 
87
        try:
 
88
            path = self.abspath(relpath)
 
89
            return open(path, 'rb')
 
90
        except (IOError, OSError),e:
 
91
            self._translate_error(e, path)
 
92
 
 
93
    def put(self, relpath, f, mode=None):
 
94
        """Copy the file-like or string object into the location.
 
95
 
 
96
        :param relpath: Location to put the contents, relative to base.
 
97
        :param f:       File-like or string object.
 
98
        """
 
99
        from bzrlib.atomicfile import AtomicFile
 
100
 
 
101
        path = relpath
 
102
        try:
 
103
            path = self.abspath(relpath)
 
104
            check_legal_path(path)
 
105
            fp = AtomicFile(path, 'wb', new_mode=mode)
 
106
        except (IOError, OSError),e:
 
107
            self._translate_error(e, path)
 
108
        try:
 
109
            self._pump(f, fp)
 
110
            fp.commit()
 
111
        finally:
 
112
            fp.close()
 
113
 
 
114
    def iter_files_recursive(self):
 
115
        """Iter the relative paths of files in the transports sub-tree."""
 
116
        queue = list(self.list_dir(u'.'))
 
117
        while queue:
 
118
            relpath = queue.pop(0)
 
119
            st = self.stat(relpath)
 
120
            if S_ISDIR(st[ST_MODE]):
 
121
                for i, basename in enumerate(self.list_dir(relpath)):
 
122
                    queue.insert(i, relpath+'/'+basename)
 
123
            else:
 
124
                yield relpath
 
125
 
 
126
    def mkdir(self, relpath, mode=None):
 
127
        """Create a directory at the given path."""
 
128
        path = relpath
 
129
        try:
 
130
            path = self.abspath(relpath)
 
131
            os.mkdir(path)
 
132
            if mode is not None:
 
133
                os.chmod(path, mode)
 
134
        except (IOError, OSError),e:
 
135
            self._translate_error(e, path)
 
136
 
 
137
    def append(self, relpath, f, mode=None):
 
138
        """Append the text in the file-like object into the final
 
139
        location.
 
140
        """
 
141
        try:
 
142
            fp = open(self.abspath(relpath), 'ab')
 
143
            if mode is not None:
 
144
                os.chmod(self.abspath(relpath), mode)
 
145
        except (IOError, OSError),e:
 
146
            self._translate_error(e, relpath)
 
147
        # win32 workaround (tell on an unwritten file returns 0)
 
148
        fp.seek(0, 2)
 
149
        result = fp.tell()
 
150
        self._pump(f, fp)
 
151
        return result
 
152
 
 
153
    def copy(self, rel_from, rel_to):
 
154
        """Copy the item at rel_from to the location at rel_to"""
 
155
        path_from = self.abspath(rel_from)
 
156
        path_to = self.abspath(rel_to)
 
157
        try:
 
158
            shutil.copy(path_from, path_to)
 
159
        except (IOError, OSError),e:
 
160
            # TODO: What about path_to?
 
161
            self._translate_error(e, path_from)
 
162
 
 
163
    def rename(self, rel_from, rel_to):
 
164
        path_from = self.abspath(rel_from)
 
165
        try:
 
166
            # *don't* call bzrlib.osutils.rename, because we want to 
 
167
            # detect errors on rename
 
168
            os.rename(path_from, self.abspath(rel_to))
 
169
        except (IOError, OSError),e:
 
170
            # TODO: What about path_to?
 
171
            self._translate_error(e, path_from)
 
172
 
 
173
    def move(self, rel_from, rel_to):
 
174
        """Move the item at rel_from to the location at rel_to"""
 
175
        path_from = self.abspath(rel_from)
 
176
        path_to = self.abspath(rel_to)
 
177
 
 
178
        try:
 
179
            # this version will delete the destination if necessary
 
180
            rename(path_from, path_to)
 
181
        except (IOError, OSError),e:
 
182
            # TODO: What about path_to?
 
183
            self._translate_error(e, path_from)
 
184
 
 
185
    def delete(self, relpath):
 
186
        """Delete the item at relpath"""
 
187
        path = relpath
 
188
        try:
 
189
            path = self.abspath(relpath)
 
190
            os.remove(path)
 
191
        except (IOError, OSError),e:
 
192
            # TODO: What about path_to?
 
193
            self._translate_error(e, path)
 
194
 
 
195
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
196
        """Copy a set of entries from self into another Transport.
 
197
 
 
198
        :param relpaths: A list/generator of entries to be copied.
 
199
        """
 
200
        if isinstance(other, LocalTransport):
 
201
            # Both from & to are on the local filesystem
 
202
            # Unfortunately, I can't think of anything faster than just
 
203
            # copying them across, one by one :(
 
204
            total = self._get_total(relpaths)
 
205
            count = 0
 
206
            for path in relpaths:
 
207
                self._update_pb(pb, 'copy-to', count, total)
 
208
                try:
 
209
                    mypath = self.abspath(path)
 
210
                    otherpath = other.abspath(path)
 
211
                    shutil.copy(mypath, otherpath)
 
212
                    if mode is not None:
 
213
                        os.chmod(otherpath, mode)
 
214
                except (IOError, OSError),e:
 
215
                    self._translate_error(e, path)
 
216
                count += 1
 
217
            return count
 
218
        else:
 
219
            return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
220
 
 
221
    def listable(self):
 
222
        """See Transport.listable."""
 
223
        return True
 
224
 
 
225
    def list_dir(self, relpath):
 
226
        """Return a list of all files at the given location.
 
227
        WARNING: many transports do not support this, so trying avoid using
 
228
        it if at all possible.
 
229
        """
 
230
        path = self.abspath(relpath)
 
231
        try:
 
232
            return [urllib.quote(entry) for entry in os.listdir(path)]
 
233
        except (IOError, OSError), e:
 
234
            self._translate_error(e, path)
 
235
 
 
236
    def stat(self, relpath):
 
237
        """Return the stat information for a file.
 
238
        """
 
239
        path = relpath
 
240
        try:
 
241
            path = self.abspath(relpath)
 
242
            return os.stat(path)
 
243
        except (IOError, OSError),e:
 
244
            self._translate_error(e, path)
 
245
 
 
246
    def lock_read(self, relpath):
 
247
        """Lock the given file for shared (read) access.
 
248
        :return: A lock object, which should be passed to Transport.unlock()
 
249
        """
 
250
        from bzrlib.lock import ReadLock
 
251
        path = relpath
 
252
        try:
 
253
            path = self.abspath(relpath)
 
254
            return ReadLock(path)
 
255
        except (IOError, OSError), e:
 
256
            self._translate_error(e, path)
 
257
 
 
258
    def lock_write(self, relpath):
 
259
        """Lock the given file for exclusive (write) access.
 
260
        WARNING: many transports do not support this, so trying avoid using it
 
261
 
 
262
        :return: A lock object, which should be passed to Transport.unlock()
 
263
        """
 
264
        from bzrlib.lock import WriteLock
 
265
        return WriteLock(self.abspath(relpath))
 
266
 
 
267
    def rmdir(self, relpath):
 
268
        """See Transport.rmdir."""
 
269
        path = relpath
 
270
        try:
 
271
            path = self.abspath(relpath)
 
272
            os.rmdir(path)
 
273
        except (IOError, OSError),e:
 
274
            self._translate_error(e, path)
 
275
 
 
276
    def _can_roundtrip_unix_modebits(self):
 
277
        if sys.platform == 'win32':
 
278
            # anyone else?
 
279
            return False
 
280
        else:
 
281
            return True
 
282
 
 
283
 
 
284
class ScratchTransport(LocalTransport):
 
285
    """A transport that works in a temporary dir and cleans up after itself.
 
286
    
 
287
    The dir only exists for the lifetime of the Python object.
 
288
    Obviously you should not put anything precious in it.
 
289
    """
 
290
 
 
291
    def __init__(self, base=None):
 
292
        if base is None:
 
293
            base = tempfile.mkdtemp()
 
294
        super(ScratchTransport, self).__init__(base)
 
295
 
 
296
    def __del__(self):
 
297
        rmtree(self.base, ignore_errors=True)
 
298
        mutter("%r destroyed" % self)
 
299
 
 
300
 
 
301
class LocalRelpathServer(Server):
 
302
    """A pretend server for local transports, using relpaths."""
 
303
 
 
304
    def get_url(self):
 
305
        """See Transport.Server.get_url."""
 
306
        return "."
 
307
 
 
308
 
 
309
class LocalAbspathServer(Server):
 
310
    """A pretend server for local transports, using absolute paths."""
 
311
 
 
312
    def get_url(self):
 
313
        """See Transport.Server.get_url."""
 
314
        return os.path.abspath("")
 
315
 
 
316
 
 
317
class LocalURLServer(Server):
 
318
    """A pretend server for local transports, using file:// urls."""
 
319
 
 
320
    def get_url(self):
 
321
        """See Transport.Server.get_url."""
 
322
        # FIXME: \ to / on windows
 
323
        return "file://%s" % os.path.abspath("")
 
324
 
 
325
 
 
326
def get_test_permutations():
 
327
    """Return the permutations to be used in testing."""
 
328
    return [(LocalTransport, LocalRelpathServer),
 
329
            (LocalTransport, LocalAbspathServer),
 
330
            (LocalTransport, LocalURLServer),
 
331
            ]