~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

Merge in format-5 work - release bzr 0.1rc1.

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
"""Implementation of Transport for the local filesystem.
 
17
"""
 
18
 
 
19
from bzrlib.transport import Transport, register_transport, \
 
20
    TransportError, NoSuchFile, FileExists
 
21
import os, errno
 
22
 
 
23
class LocalTransportError(TransportError):
 
24
    pass
 
25
 
 
26
class LocalTransport(Transport):
 
27
    """This is the transport agent for local filesystem access."""
 
28
 
 
29
    def __init__(self, base):
 
30
        """Set the base path where files will be stored."""
 
31
        if base.startswith('file://'):
 
32
            base = base[7:]
 
33
        # realpath is incompatible with symlinks. When we traverse
 
34
        # up we might be able to normpath stuff. RBC 20051003
 
35
        super(LocalTransport, self).__init__(
 
36
            os.path.normpath(os.path.abspath(base)))
 
37
 
 
38
    def should_cache(self):
 
39
        return False
 
40
 
 
41
    def clone(self, offset=None):
 
42
        """Return a new LocalTransport with root at self.base + offset
 
43
        Because the local filesystem does not require a connection, 
 
44
        we can just return a new object.
 
45
        """
 
46
        if offset is None:
 
47
            return LocalTransport(self.base)
 
48
        else:
 
49
            return LocalTransport(self.abspath(offset))
 
50
 
 
51
    def abspath(self, relpath):
 
52
        """Return the full url to the given relative path.
 
53
        This can be supplied with a string or a list
 
54
        """
 
55
        if isinstance(relpath, basestring):
 
56
            relpath = [relpath]
 
57
        return os.path.join(self.base, *relpath)
 
58
 
 
59
    def relpath(self, abspath):
 
60
        """Return the local path portion from a given absolute path.
 
61
        """
 
62
        from bzrlib.branch import _relpath
 
63
        return _relpath(self.base, abspath)
 
64
 
 
65
    def has(self, relpath):
 
66
        return os.access(self.abspath(relpath), os.F_OK)
 
67
 
 
68
    def get(self, relpath):
 
69
        """Get the file at the given relative path.
 
70
 
 
71
        :param relpath: The relative path to the file
 
72
        """
 
73
        try:
 
74
            path = self.abspath(relpath)
 
75
            return open(path, 'rb')
 
76
        except IOError,e:
 
77
            if e.errno == errno.ENOENT:
 
78
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
 
79
            raise LocalTransportError(orig_error=e)
 
80
 
 
81
    def get_partial(self, relpath, start, length=None):
 
82
        """Get just part of a file.
 
83
 
 
84
        :param relpath: Path to the file, relative to base
 
85
        :param start: The starting position to read from
 
86
        :param length: The length to read. A length of None indicates
 
87
                       read to the end of the file.
 
88
        :return: A file-like object containing at least the specified bytes.
 
89
                 Some implementations may return objects which can be read
 
90
                 past this length, but this is not guaranteed.
 
91
        """
 
92
        # LocalTransport.get_partial() doesn't care about the length
 
93
        # argument, because it is using a local file, and thus just
 
94
        # returns the file seek'ed to the appropriate location.
 
95
        try:
 
96
            path = self.abspath(relpath)
 
97
            f = open(path, 'rb')
 
98
            f.seek(start, 0)
 
99
            return f
 
100
        except IOError,e:
 
101
            if e.errno == errno.ENOENT:
 
102
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
 
103
            raise LocalTransportError(orig_error=e)
 
104
 
 
105
    def put(self, relpath, f):
 
106
        """Copy the file-like or string object into the location.
 
107
 
 
108
        :param relpath: Location to put the contents, relative to base.
 
109
        :param f:       File-like or string object.
 
110
        """
 
111
        from bzrlib.atomicfile import AtomicFile
 
112
 
 
113
        try:
 
114
            path = self.abspath(relpath)
 
115
            fp = AtomicFile(path, 'wb')
 
116
        except IOError, e:
 
117
            if e.errno == errno.ENOENT:
 
118
                raise NoSuchFile('File %r does not exist' % path, orig_error=e)
 
119
            raise LocalTransportError(orig_error=e)
 
120
        try:
 
121
            self._pump(f, fp)
 
122
            fp.commit()
 
123
        finally:
 
124
            fp.close()
 
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
                shutil.copy(self.abspath(path), other.abspath(path))
 
187
                count += 1
 
188
            return count
 
189
        else:
 
190
            return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
 
191
 
 
192
    def listable(self):
 
193
        """See Transport.listable."""
 
194
        return True
 
195
 
 
196
    def list_dir(self, relpath):
 
197
        """Return a list of all files at the given location.
 
198
        WARNING: many transports do not support this, so trying avoid using
 
199
        it if at all possible.
 
200
        """
 
201
        try:
 
202
            return os.listdir(self.abspath(relpath))
 
203
        except OSError,e:
 
204
            raise LocalTransportError(orig_error=e)
 
205
 
 
206
    def stat(self, relpath):
 
207
        """Return the stat information for a file.
 
208
        """
 
209
        try:
 
210
            return os.stat(self.abspath(relpath))
 
211
        except OSError,e:
 
212
            raise LocalTransportError(orig_error=e)
 
213
 
 
214
    def lock_read(self, relpath):
 
215
        """Lock the given file for shared (read) access.
 
216
        :return: A lock object, which should be passed to Transport.unlock()
 
217
        """
 
218
        from bzrlib.lock import ReadLock
 
219
        return ReadLock(self.abspath(relpath))
 
220
 
 
221
    def lock_write(self, relpath):
 
222
        """Lock the given file for exclusive (write) access.
 
223
        WARNING: many transports do not support this, so trying avoid using it
 
224
 
 
225
        :return: A lock object, which should be passed to Transport.unlock()
 
226
        """
 
227
        from bzrlib.lock import WriteLock
 
228
        return WriteLock(self.abspath(relpath))
 
229
 
 
230
# If nothing else matches, try the LocalTransport
 
231
register_transport(None, LocalTransport)
 
232
register_transport('file://', LocalTransport)