/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Robert Collins
  • Date: 2006-05-23 12:21:01 UTC
  • mto: (1755.1.2 integration)
  • mto: This revision was merged to the branch mainline in revision 1757.
  • Revision ID: robertc@robertcollins.net-20060523122101-c9a7db6671267a47
Tune commit a little by improving performance of inventory.iter_entries.

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