~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Martin Pool
  • Date: 2005-06-11 01:33:22 UTC
  • Revision ID: mbp@sourcefrog.net-20050611013322-f12014bf65accd0c
- don't show progress bar unless completion is known

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
 
            ]