~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/local.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-09 04:08:15 UTC
  • Revision ID: mbp@sourcefrog.net-20050309040815-13242001617e4a06
import from baz patch-364

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