~ubuntu-branches/ubuntu/hardy/bzr/hardy-updates

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: Bazaar Package Importer
  • Author(s): Jeff Bailey
  • Date: 2005-11-07 13:17:53 UTC
  • Revision ID: james.westby@ubuntu.com-20051107131753-qsy145z1rfug5i27
Tags: upstream-0.6.2
ImportĀ upstreamĀ versionĀ 0.6.2

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
                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)