~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Robert Collins
  • Date: 2005-10-17 23:32:06 UTC
  • mto: This revision was merged to the branch mainline in revision 1462.
  • Revision ID: robertc@robertcollins.net-20051017233206-cbd6059e27bd4e39
Branch.remove has been moved to WorkingTree.

Part of this is providing WorkingTree with locking primitives,
to allow the use of the needs_write_lock decorator.
(Robert Collins)

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