~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Martin Pool
  • Date: 2005-04-28 07:24:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050428072453-7b99afa993a1e549
todo

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)
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 = 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
 
                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
 
 
231
 
class ScratchTransport(LocalTransport):
232
 
    """A transport that works in a temporary dir and cleans up after itself.
233
 
    
234
 
    The dir only exists for the lifetime of the Python object.
235
 
    Obviously you should not put anything precious in it.
236
 
    """
237
 
 
238
 
    def __init__(self, base=None):
239
 
        if base is None:
240
 
            base = tempfile.mkdtemp()
241
 
        super(ScratchTransport, self).__init__(base)
242
 
 
243
 
    def __del__(self):
244
 
        shutil.rmtree(self.base, ignore_errors=True)
245
 
        mutter("%r destroyed" % self)