~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

[merge] fix \t in commit messages

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 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 errno
 
23
import shutil
 
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, register_transport, \
 
30
    TransportError, NoSuchFile, FileExists
 
31
from bzrlib.osutils import abspath
 
32
 
 
33
class LocalTransportError(TransportError):
 
34
    pass
 
35
 
 
36
 
 
37
class LocalTransport(Transport):
 
38
    """This is the transport agent for local filesystem access."""
 
39
 
 
40
    def __init__(self, base):
 
41
        """Set the base path where files will be stored."""
 
42
        if base.startswith('file://'):
 
43
            base = base[7:]
 
44
        # realpath is incompatible with symlinks. When we traverse
 
45
        # up we might be able to normpath stuff. RBC 20051003
 
46
        super(LocalTransport, self).__init__(
 
47
            os.path.normpath(abspath(base)))
 
48
 
 
49
    def should_cache(self):
 
50
        return False
 
51
 
 
52
    def clone(self, offset=None):
 
53
        """Return a new LocalTransport with root at self.base + offset
 
54
        Because the local filesystem does not require a connection, 
 
55
        we can just return a new object.
 
56
        """
 
57
        if offset is None:
 
58
            return LocalTransport(self.base)
 
59
        else:
 
60
            return LocalTransport(self.abspath(offset))
 
61
 
 
62
    def abspath(self, relpath):
 
63
        """Return the full url to the given relative URL.
 
64
        This can be supplied with a string or a list
 
65
        """
 
66
        assert isinstance(relpath, basestring), (type(relpath), relpath)
 
67
        return os.path.join(self.base, urllib.unquote(relpath))
 
68
 
 
69
    def relpath(self, abspath):
 
70
        """Return the local path portion from a given absolute path.
 
71
        """
 
72
        from bzrlib.osutils import relpath
 
73
        if abspath is None:
 
74
            abspath = '.'
 
75
        return relpath(self.base, abspath)
 
76
 
 
77
    def has(self, relpath):
 
78
        return os.access(self.abspath(relpath), os.F_OK)
 
79
 
 
80
    def get(self, relpath):
 
81
        """Get the file at the given relative path.
 
82
 
 
83
        :param relpath: The relative path to the file
 
84
        """
 
85
        try:
 
86
            path = self.abspath(relpath)
 
87
            return open(path, 'rb')
 
88
        except IOError,e:
 
89
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
90
                raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
 
91
            raise LocalTransportError(orig_error=e)
 
92
 
 
93
    def put(self, relpath, f):
 
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
        try:
 
102
            path = self.abspath(relpath)
 
103
            fp = AtomicFile(path, 'wb')
 
104
        except IOError, e:
 
105
            if e.errno == errno.ENOENT:
 
106
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
 
107
            raise LocalTransportError(orig_error=e)
 
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('.'))
 
117
        while queue:
 
118
            relpath = urllib.quote(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):
 
127
        """Create a directory at the given path."""
 
128
        try:
 
129
            os.mkdir(self.abspath(relpath))
 
130
        except OSError,e:
 
131
            if e.errno == errno.EEXIST:
 
132
                raise FileExists(orig_error=e)
 
133
            elif e.errno == errno.ENOENT:
 
134
                raise NoSuchFile(orig_error=e)
 
135
            raise LocalTransportError(orig_error=e)
 
136
 
 
137
    def append(self, relpath, f):
 
138
        """Append the text in the file-like object into the final
 
139
        location.
 
140
        """
 
141
        fp = open(self.abspath(relpath), 'ab')
 
142
        self._pump(f, fp)
 
143
 
 
144
    def copy(self, rel_from, rel_to):
 
145
        """Copy the item at rel_from to the location at rel_to"""
 
146
        import shutil
 
147
        path_from = self.abspath(rel_from)
 
148
        path_to = self.abspath(rel_to)
 
149
        try:
 
150
            shutil.copy(path_from, path_to)
 
151
        except OSError,e:
 
152
            raise LocalTransportError(orig_error=e)
 
153
 
 
154
    def move(self, rel_from, rel_to):
 
155
        """Move the item at rel_from to the location at rel_to"""
 
156
        path_from = self.abspath(rel_from)
 
157
        path_to = self.abspath(rel_to)
 
158
 
 
159
        try:
 
160
            os.rename(path_from, path_to)
 
161
        except OSError,e:
 
162
            raise LocalTransportError(orig_error=e)
 
163
 
 
164
    def delete(self, relpath):
 
165
        """Delete the item at relpath"""
 
166
        try:
 
167
            os.remove(self.abspath(relpath))
 
168
        except OSError,e:
 
169
            raise LocalTransportError(orig_error=e)
 
170
 
 
171
    def copy_to(self, relpaths, other, pb=None):
 
172
        """Copy a set of entries from self into another Transport.
 
173
 
 
174
        :param relpaths: A list/generator of entries to be copied.
 
175
        """
 
176
        if isinstance(other, LocalTransport):
 
177
            # Both from & to are on the local filesystem
 
178
            # Unfortunately, I can't think of anything faster than just
 
179
            # copying them across, one by one :(
 
180
            import shutil
 
181
 
 
182
            total = self._get_total(relpaths)
 
183
            count = 0
 
184
            for path in relpaths:
 
185
                self._update_pb(pb, 'copy-to', count, total)
 
186
                try:
 
187
                    shutil.copy(self.abspath(path), other.abspath(path))
 
188
                except IOError, e:
 
189
                    if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
190
                        raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
 
191
                    raise LocalTransportError(orig_error=e)
 
192
                count += 1
 
193
            return count
 
194
        else:
 
195
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
 
196
 
 
197
    def listable(self):
 
198
        """See Transport.listable."""
 
199
        return True
 
200
 
 
201
    def list_dir(self, relpath):
 
202
        """Return a list of all files at the given location.
 
203
        WARNING: many transports do not support this, so trying avoid using
 
204
        it if at all possible.
 
205
        """
 
206
        try:
 
207
            return os.listdir(self.abspath(relpath))
 
208
        except OSError,e:
 
209
            raise LocalTransportError(orig_error=e)
 
210
 
 
211
    def stat(self, relpath):
 
212
        """Return the stat information for a file.
 
213
        """
 
214
        try:
 
215
            return os.stat(self.abspath(relpath))
 
216
        except OSError,e:
 
217
            raise LocalTransportError(orig_error=e)
 
218
 
 
219
    def lock_read(self, relpath):
 
220
        """Lock the given file for shared (read) access.
 
221
        :return: A lock object, which should be passed to Transport.unlock()
 
222
        """
 
223
        from bzrlib.lock import ReadLock
 
224
        return ReadLock(self.abspath(relpath))
 
225
 
 
226
    def lock_write(self, relpath):
 
227
        """Lock the given file for exclusive (write) access.
 
228
        WARNING: many transports do not support this, so trying avoid using it
 
229
 
 
230
        :return: A lock object, which should be passed to Transport.unlock()
 
231
        """
 
232
        from bzrlib.lock import WriteLock
 
233
        return WriteLock(self.abspath(relpath))
 
234
 
 
235
 
 
236
class ScratchTransport(LocalTransport):
 
237
    """A transport that works in a temporary dir and cleans up after itself.
 
238
    
 
239
    The dir only exists for the lifetime of the Python object.
 
240
    Obviously you should not put anything precious in it.
 
241
    """
 
242
 
 
243
    def __init__(self, base=None):
 
244
        if base is None:
 
245
            base = tempfile.mkdtemp()
 
246
        super(ScratchTransport, self).__init__(base)
 
247
 
 
248
    def __del__(self):
 
249
        shutil.rmtree(self.base, ignore_errors=True)
 
250
        mutter("%r destroyed" % self)