~bzr-pqm/bzr/bzr.dev

5193.4.6 by Mattias Eriksson
Register a generic gio+ handler and raise an InvalidURL inside the gio init
1
# Copyright (C) 2010 Canonical Ltd.
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
5193.4.6 by Mattias Eriksson
Register a generic gio+ handler and raise an InvalidURL inside the gio init
16
#
17
# Author: Mattias Eriksson
18
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
19
"""Implementation of Transport over gio.
20
21
Written by Mattias Eriksson <snaggen@acc.umu.se> based on the ftp transport.
22
5193.4.5 by Mattias Eriksson
pep8
23
It provides the gio+XXX:// protocols where XXX is any of the protocols
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
24
supported by gio.
25
"""
6379.6.7 by Jelmer Vernooij
Move importing from future until after doc string, otherwise the doc string will disappear.
26
27
from __future__ import absolute_import
28
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
29
from cStringIO import StringIO
30
import os
31
import random
32
import stat
33
import time
5193.4.4 by Mattias Eriksson
Remove username and password from the url we are sending to GIO
34
import urlparse
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
35
36
from bzrlib import (
37
    config,
38
    errors,
39
    osutils,
40
    urlutils,
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
41
    debug,
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
42
    ui,
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
43
    )
5390.2.1 by Alexander Belchenko
`decode` parameter to get() method in FtpTransport and GioTransport classes is deprecated.
44
from bzrlib.symbol_versioning import (
45
    DEPRECATED_PARAMETER,
46
    deprecated_in,
47
    deprecated_passed,
48
    warn,
49
    )
6039.1.4 by Jelmer Vernooij
Remove unused imports.
50
from bzrlib.trace import mutter
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
51
from bzrlib.transport import (
52
    FileStream,
53
    ConnectedTransport,
54
    _file_streams,
55
    )
56
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
57
from bzrlib.tests.test_server import TestServer
58
5193.4.19 by Mattias Eriksson
Explicit check for glib and gio and raise DependencyNotPresent
59
try:
60
    import glib
61
except ImportError, e:
62
    raise errors.DependencyNotPresent('glib', e)
63
try:
64
    import gio
65
except ImportError, e:
66
    raise errors.DependencyNotPresent('gio', e)
67
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
68
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
69
class GioLocalURLServer(TestServer):
70
    """A pretend server for local transports, using file:// urls.
71
72
    Of course no actual server is required to access the local filesystem, so
73
    this just exists to tell the test code how to get to it.
74
    """
75
76
    def start_server(self):
77
        pass
78
79
    def get_url(self):
80
        """See Transport.Server.get_url."""
81
        return "gio+" + urlutils.local_path_to_url('')
82
5193.4.5 by Mattias Eriksson
pep8
83
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
84
class GioFileStream(FileStream):
85
    """A file stream object returned by open_write_stream.
86
87
    This version uses GIO to perform writes.
88
    """
89
90
    def __init__(self, transport, relpath):
91
        FileStream.__init__(self, transport, relpath)
92
        self.gio_file = transport._get_GIO(relpath)
93
        self.stream = self.gio_file.create()
94
95
    def _close(self):
96
        self.stream.close()
97
98
    def write(self, bytes):
99
        try:
100
            #Using pump_string_file seems to make things crash
101
            osutils.pumpfile(StringIO(bytes), self.stream)
102
        except gio.Error, e:
103
            #self.transport._translate_gio_error(e,self.relpath)
104
            raise errors.BzrError(str(e))
105
5193.4.5 by Mattias Eriksson
pep8
106
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
107
class GioStatResult(object):
108
109
    def __init__(self, f):
5193.4.5 by Mattias Eriksson
pep8
110
        info = f.query_info('standard::size,standard::type')
111
        self.st_size = info.get_size()
112
        type = info.get_file_type()
113
        if (type == gio.FILE_TYPE_REGULAR):
114
            self.st_mode = stat.S_IFREG
115
        elif type == gio.FILE_TYPE_DIRECTORY:
116
            self.st_mode = stat.S_IFDIR
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
117
118
119
class GioTransport(ConnectedTransport):
120
    """This is the transport agent for gio+XXX:// access."""
121
122
    def __init__(self, base, _from_transport=None):
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
123
        """Initialize the GIO transport and make sure the url is correct."""
124
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
125
        if not base.startswith('gio+'):
126
            raise ValueError(base)
127
5193.4.17 by Mattias Eriksson
Use urlparse for both parsing and unparsing, since urlutils do decoding and other things.
128
        (scheme, netloc, path, params, query, fragment) = \
129
                urlparse.urlparse(base[len('gio+'):], allow_fragments=False)
130
        if '@' in netloc:
131
            user, netloc = netloc.rsplit('@', 1)
5193.4.6 by Mattias Eriksson
Register a generic gio+ handler and raise an InvalidURL inside the gio init
132
        #Seems it is not possible to list supported backends for GIO
133
        #so a hardcoded list it is then.
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
134
        gio_backends = ['dav', 'file', 'ftp', 'obex', 'sftp', 'ssh', 'smb']
5193.4.6 by Mattias Eriksson
Register a generic gio+ handler and raise an InvalidURL inside the gio init
135
        if scheme not in gio_backends:
5193.4.20 by Mattias Eriksson
Remove not needed \ when breaking lines
136
            raise errors.InvalidURL(base,
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
137
                    extra="GIO support is only available for " + \
138
                    ', '.join(gio_backends))
5193.4.5 by Mattias Eriksson
pep8
139
5193.4.4 by Mattias Eriksson
Remove username and password from the url we are sending to GIO
140
        #Remove the username and password from the url we send to GIO
5193.4.17 by Mattias Eriksson
Use urlparse for both parsing and unparsing, since urlutils do decoding and other things.
141
        #by rebuilding the url again.
5193.4.5 by Mattias Eriksson
pep8
142
        u = (scheme, netloc, path, '', '', '')
5193.4.4 by Mattias Eriksson
Remove username and password from the url we are sending to GIO
143
        self.url = urlparse.urlunparse(u)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
144
5193.4.6 by Mattias Eriksson
Register a generic gio+ handler and raise an InvalidURL inside the gio init
145
        # And finally initialize super
146
        super(GioTransport, self).__init__(base,
147
            _from_transport=_from_transport)
148
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
149
    def _relpath_to_url(self, relpath):
150
        full_url = urlutils.join(self.url, relpath)
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
151
        if isinstance(full_url, unicode):
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
152
            raise errors.InvalidURL(full_url)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
153
        return full_url
154
155
    def _get_GIO(self, relpath):
156
        """Return the ftplib.GIO instance for this object."""
157
        # Ensures that a connection is established
158
        connection = self._get_connection()
159
        if connection is None:
160
            # First connection ever
161
            connection, credentials = self._create_connection()
162
            self._set_connection(connection, credentials)
163
        fileurl = self._relpath_to_url(relpath)
5193.4.5 by Mattias Eriksson
pep8
164
        file = gio.File(fileurl)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
165
        return file
166
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
167
    def _auth_cb(self, op, message, default_user, default_domain, flags):
5193.4.5 by Mattias Eriksson
pep8
168
        #really use bzrlib.auth get_password for this
169
        #or possibly better gnome-keyring?
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
170
        auth = config.AuthenticationConfig()
6055.2.7 by Jelmer Vernooij
Change parse_url to URL.from_string.
171
        parsed_url = urlutils.URL.from_string(self.url)
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
172
        user = None
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
173
        if (flags & gio.ASK_PASSWORD_NEED_USERNAME and
174
                flags & gio.ASK_PASSWORD_NEED_DOMAIN):
6055.2.1 by Jelmer Vernooij
Add UnparsedUrl.
175
            prompt = (u'%s' % (parsed_url.scheme.upper(),) +
176
                      u' %(host)s DOMAIN\\username')
177
            user_and_domain = auth.get_user(parsed_url.scheme,
178
                parsed_url.host, port=parsed_url.port, ask=True,
179
                prompt=prompt)
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
180
            (domain, user) = user_and_domain.split('\\', 1)
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
181
            op.set_username(user)
182
            op.set_domain(domain)
183
        elif flags & gio.ASK_PASSWORD_NEED_USERNAME:
6055.2.1 by Jelmer Vernooij
Add UnparsedUrl.
184
            user = auth.get_user(parsed_url.scheme, parsed_url.host,
185
                    port=parsed_url.port, ask=True)
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
186
            op.set_username(user)
187
        elif flags & gio.ASK_PASSWORD_NEED_DOMAIN:
188
            #Don't know how common this case is, but anyway
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
189
            #a DOMAIN and a username prompt should be the
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
190
            #same so I will missuse the ui_factory get_username
191
            #a little bit here.
6055.2.1 by Jelmer Vernooij
Add UnparsedUrl.
192
            prompt = (u'%s' % (parsed_url.scheme.upper(),) +
193
                      u' %(host)s DOMAIN')
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
194
            domain = ui.ui_factory.get_username(prompt=prompt)
195
            op.set_domain(domain)
196
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
197
        if flags & gio.ASK_PASSWORD_NEED_PASSWORD:
5193.4.8 by Mattias Eriksson
Rework the authentication code to use the native bzrlib methods. This also means that it will use gnome-keyring if bzr-gtk plugins are available
198
            if user is None:
199
                user = op.get_username()
6055.2.1 by Jelmer Vernooij
Add UnparsedUrl.
200
            password = auth.get_password(parsed_url.scheme, parsed_url.host,
201
                    user, port=parsed_url.port)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
202
            op.set_password(password)
203
        op.reply(gio.MOUNT_OPERATION_HANDLED)
5193.4.5 by Mattias Eriksson
pep8
204
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
205
    def _mount_done_cb(self, obj, res):
206
        try:
207
            obj.mount_enclosing_volume_finish(res)
5193.4.24 by Mattias Eriksson
Make sure the loop.quit is run before raising the exception
208
            self.loop.quit()
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
209
        except gio.Error, e:
5193.4.24 by Mattias Eriksson
Make sure the loop.quit is run before raising the exception
210
            self.loop.quit()
5193.4.23 by Mattias Eriksson
Cant use the translation, so just raise a BzrError instead
211
            raise errors.BzrError("Failed to mount the given location: " + str(e));
5193.4.5 by Mattias Eriksson
pep8
212
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
213
    def _create_connection(self, credentials=None):
214
        if credentials is None:
6055.2.1 by Jelmer Vernooij
Add UnparsedUrl.
215
            user, password = self._parsed_url.user, self._parsed_url.password
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
216
        else:
217
            user, password = credentials
218
219
        try:
5193.4.5 by Mattias Eriksson
pep8
220
            connection = gio.File(self.url)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
221
            mount = None
222
            try:
223
                mount = connection.find_enclosing_mount()
224
            except gio.Error, e:
225
                if (e.code == gio.ERROR_NOT_MOUNTED):
5193.4.18 by Mattias Eriksson
Remove dependency on gtk+, run a glib.MainLoop instead
226
                    self.loop = glib.MainLoop()
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
227
                    ui.ui_factory.show_message('Mounting %s using GIO' % \
228
                            self.url)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
229
                    op = gio.MountOperation()
5193.4.2 by Mattias Eriksson
Set the username and password before mount if we have them available
230
                    if user:
231
                        op.set_username(user)
232
                    if password:
233
                        op.set_password(password)
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
234
                    op.connect('ask-password', self._auth_cb)
5193.4.20 by Mattias Eriksson
Remove not needed \ when breaking lines
235
                    m = connection.mount_enclosing_volume(op,
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
236
                            self._mount_done_cb)
5193.4.18 by Mattias Eriksson
Remove dependency on gtk+, run a glib.MainLoop instead
237
                    self.loop.run()
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
238
        except gio.Error, e:
239
            raise errors.TransportError(msg="Error setting up connection:"
240
                                        " %s" % str(e), orig_error=e)
241
        return connection, (user, password)
242
5247.2.12 by Vincent Ladeuil
Ensure that all transports close their underlying connection.
243
    def disconnect(self):
244
        # FIXME: Nothing seems to be necessary here, which sounds a bit strange
245
        # -- vila 20100601
246
        pass
247
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
248
    def _reconnect(self):
5247.2.12 by Vincent Ladeuil
Ensure that all transports close their underlying connection.
249
        # FIXME: This doesn't seem to be used -- vila 20100601
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
250
        """Create a new connection with the previously used credentials"""
251
        credentials = self._get_credentials()
252
        connection, credentials = self._create_connection(credentials)
253
        self._set_connection(connection, credentials)
254
255
    def _remote_path(self, relpath):
6055.2.19 by Jelmer Vernooij
Use clone
256
        return self._parsed_url.clone(relpath).path
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
257
258
    def has(self, relpath):
259
        """Does the target location exist?"""
260
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
261
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
262
                mutter('GIO has check: %s' % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
263
            f = self._get_GIO(relpath)
264
            st = GioStatResult(f)
5193.4.5 by Mattias Eriksson
pep8
265
            if stat.S_ISREG(st.st_mode) or stat.S_ISDIR(st.st_mode):
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
266
                return True
267
            return False
268
        except gio.Error, e:
269
            if e.code == gio.ERROR_NOT_FOUND:
270
                return False
271
            else:
272
                self._translate_gio_error(e, relpath)
273
5390.2.1 by Alexander Belchenko
`decode` parameter to get() method in FtpTransport and GioTransport classes is deprecated.
274
    def get(self, relpath, decode=DEPRECATED_PARAMETER, retries=0):
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
275
        """Get the file at the given relative path.
276
277
        :param relpath: The relative path to the file
278
        :param retries: Number of retries after temporary failures so far
279
                        for this operation.
280
281
        We're meant to return a file-like object which bzr will
282
        then read from. For now we do this via the magic of StringIO
283
        """
5390.2.1 by Alexander Belchenko
`decode` parameter to get() method in FtpTransport and GioTransport classes is deprecated.
284
        if deprecated_passed(decode):
285
            warn(deprecated_in((2,3,0)) %
286
                 '"decode" parameter to GioTransport.get()',
287
                 DeprecationWarning, stacklevel=2)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
288
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
289
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
290
                mutter("GIO get: %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
291
            f = self._get_GIO(relpath)
292
            fin = f.read()
293
            buf = fin.read()
294
            fin.close()
295
            ret = StringIO(buf)
296
            return ret
297
        except gio.Error, e:
5193.4.9 by Mattias Eriksson
Cleaned up some user messages
298
            #If we get a not mounted here it might mean
299
            #that a bad path has been entered (or that mount failed)
300
            if (e.code == gio.ERROR_NOT_MOUNTED):
5193.4.20 by Mattias Eriksson
Remove not needed \ when breaking lines
301
                raise errors.PathError(relpath,
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
302
                  extra='Failed to get file, make sure the path is correct. ' \
303
                  + str(e))
5193.4.9 by Mattias Eriksson
Cleaned up some user messages
304
            else:
305
                self._translate_gio_error(e, relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
306
307
    def put_file(self, relpath, fp, mode=None):
308
        """Copy the file-like object into the location.
309
310
        :param relpath: Location to put the contents, relative to base.
311
        :param fp:       File-like or string object.
312
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
313
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
314
            mutter("GIO put_file %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
315
        tmppath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
5193.4.5 by Mattias Eriksson
pep8
316
                    os.getpid(), random.randint(0, 0x7FFFFFFF))
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
317
        f = None
318
        fout = None
319
        try:
5268.1.1 by Vincent Ladeuil
Fix gio-related test failures.
320
            closed = True
5261.1.1 by Robert Collins
Python 2.4 friendliness is important to bzr.
321
            try:
322
                f = self._get_GIO(tmppath)
323
                fout = f.create()
324
                closed = False
325
                length = self._pump(fp, fout)
326
                fout.close()
327
                closed = True
328
                self.stat(tmppath)
329
                dest = self._get_GIO(relpath)
330
                f.move(dest, flags=gio.FILE_COPY_OVERWRITE)
331
                f = None
332
                if mode is not None:
333
                    self._setmode(relpath, mode)
334
                return length
335
            except gio.Error, e:
336
                self._translate_gio_error(e, relpath)
5193.4.14 by Mattias Eriksson
Cleanup exception handling in put_file
337
        finally:
338
            if not closed and fout is not None:
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
339
                fout.close()
5193.4.14 by Mattias Eriksson
Cleanup exception handling in put_file
340
            if f is not None and f.query_exists():
341
                f.delete()
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
342
343
    def mkdir(self, relpath, mode=None):
344
        """Create a directory at the given path."""
345
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
346
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
347
                mutter("GIO mkdir: %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
348
            f = self._get_GIO(relpath)
349
            f.make_directory()
350
            self._setmode(relpath, mode)
351
        except gio.Error, e:
352
            self._translate_gio_error(e, relpath)
353
354
    def open_write_stream(self, relpath, mode=None):
355
        """See Transport.open_write_stream."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
356
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
357
            mutter("GIO open_write_stream %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
358
        if mode is not None:
359
            self._setmode(relpath, mode)
360
        result = GioFileStream(self, relpath)
361
        _file_streams[self.abspath(relpath)] = result
362
        return result
363
364
    def recommended_page_size(self):
365
        """See Transport.recommended_page_size().
366
367
        For FTP we suggest a large page size to reduce the overhead
368
        introduced by latency.
369
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
370
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
371
            mutter("GIO recommended_page")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
372
        return 64 * 1024
373
374
    def rmdir(self, relpath):
375
        """Delete the directory at rel_path"""
376
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
377
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
378
                mutter("GIO rmdir %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
379
            st = self.stat(relpath)
380
            if stat.S_ISDIR(st.st_mode):
381
                f = self._get_GIO(relpath)
382
                f.delete()
383
            else:
384
                raise errors.NotADirectory(relpath)
385
        except gio.Error, e:
386
            self._translate_gio_error(e, relpath)
387
        except errors.NotADirectory, e:
388
            #just pass it forward
389
            raise e
390
        except Exception, e:
5193.4.5 by Mattias Eriksson
pep8
391
            mutter('failed to rmdir %s: %s' % (relpath, e))
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
392
            raise errors.PathError(relpath)
393
394
    def append_file(self, relpath, file, mode=None):
395
        """Append the text in the file-like object into the final
396
        location.
397
        """
398
        #GIO append_to seems not to append but to truncate
399
        #Work around this.
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
400
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
401
            mutter("GIO append_file: %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
402
        tmppath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
5193.4.5 by Mattias Eriksson
pep8
403
                    os.getpid(), random.randint(0, 0x7FFFFFFF))
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
404
        try:
405
            result = 0
406
            fo = self._get_GIO(tmppath)
407
            fi = self._get_GIO(relpath)
408
            fout = fo.create()
5193.4.5 by Mattias Eriksson
pep8
409
            try:
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
410
                info = GioStatResult(fi)
411
                result = info.st_size
412
                fin = fi.read()
5193.4.15 by Mattias Eriksson
Make append_file more clear by adding comment explaining things
413
                self._pump(fin, fout)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
414
                fin.close()
5193.4.19 by Mattias Eriksson
Explicit check for glib and gio and raise DependencyNotPresent
415
            #This separate except is to catch and ignore the
416
            #gio.ERROR_NOT_FOUND for the already existing file.
5193.4.15 by Mattias Eriksson
Make append_file more clear by adding comment explaining things
417
            #It is valid to open a non-existing file for append.
5193.4.19 by Mattias Eriksson
Explicit check for glib and gio and raise DependencyNotPresent
418
            #This is caused by the broken gio append_to...
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
419
            except gio.Error, e:
420
                if e.code != gio.ERROR_NOT_FOUND:
421
                    self._translate_gio_error(e, relpath)
5193.4.5 by Mattias Eriksson
pep8
422
            length = self._pump(file, fout)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
423
            fout.close()
5193.4.16 by Mattias Eriksson
Improved append safety by moving size check before the move
424
            info = GioStatResult(fo)
5193.4.5 by Mattias Eriksson
pep8
425
            if info.st_size != result + length:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
426
                raise errors.BzrError("Failed to append size after " \
427
                      "(%d) is not original (%d) + written (%d) total (%d)" % \
428
                      (info.st_size, result, length, result + length))
5193.4.16 by Mattias Eriksson
Improved append safety by moving size check before the move
429
            fo.move(fi, flags=gio.FILE_COPY_OVERWRITE)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
430
            return result
431
        except gio.Error, e:
432
            self._translate_gio_error(e, relpath)
433
434
    def _setmode(self, relpath, mode):
435
        """Set permissions on a path.
436
437
        Only set permissions on Unix systems
438
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
439
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
440
            mutter("GIO _setmode %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
441
        if mode:
442
            try:
443
                f = self._get_GIO(relpath)
5193.4.5 by Mattias Eriksson
pep8
444
                f.set_attribute_uint32(gio.FILE_ATTRIBUTE_UNIX_MODE, mode)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
445
            except gio.Error, e:
446
                if e.code == gio.ERROR_NOT_SUPPORTED:
447
                    # Command probably not available on this server
448
                    mutter("GIO Could not set permissions to %s on %s. %s",
449
                        oct(mode), self._remote_path(relpath), str(e))
450
                else:
451
                    self._translate_gio_error(e, relpath)
452
453
    def rename(self, rel_from, rel_to):
454
        """Rename without special overwriting"""
455
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
456
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
457
                mutter("GIO move (rename): %s => %s", rel_from, rel_to)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
458
            f = self._get_GIO(rel_from)
459
            t = self._get_GIO(rel_to)
460
            f.move(t)
461
        except gio.Error, e:
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
462
            self._translate_gio_error(e, rel_from)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
463
464
    def move(self, rel_from, rel_to):
465
        """Move the item at rel_from to the location at rel_to"""
466
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
467
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
468
                mutter("GIO move: %s => %s", rel_from, rel_to)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
469
            f = self._get_GIO(rel_from)
470
            t = self._get_GIO(rel_to)
471
            f.move(t, flags=gio.FILE_COPY_OVERWRITE)
472
        except gio.Error, e:
473
            self._translate_gio_error(e, relfrom)
474
475
    def delete(self, relpath):
476
        """Delete the item at relpath"""
477
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
478
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
479
                mutter("GIO delete: %s", relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
480
            f = self._get_GIO(relpath)
481
            f.delete()
482
        except gio.Error, e:
483
            self._translate_gio_error(e, relpath)
484
485
    def external_url(self):
486
        """See bzrlib.transport.Transport.external_url."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
487
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
488
            mutter("GIO external_url", self.base)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
489
        # GIO external url
490
        return self.base
491
492
    def listable(self):
493
        """See Transport.listable."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
494
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
495
            mutter("GIO listable")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
496
        return True
497
498
    def list_dir(self, relpath):
499
        """See Transport.list_dir."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
500
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
501
            mutter("GIO list_dir")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
502
        try:
503
            entries = []
5193.4.5 by Mattias Eriksson
pep8
504
            f = self._get_GIO(relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
505
            children = f.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_NAME)
506
            for child in children:
507
                entries.append(urlutils.escape(child.get_name()))
508
            return entries
509
        except gio.Error, e:
510
            self._translate_gio_error(e, relpath)
511
512
    def iter_files_recursive(self):
513
        """See Transport.iter_files_recursive.
514
515
        This is cargo-culted from the SFTP transport"""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
516
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
517
            mutter("GIO iter_files_recursive")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
518
        queue = list(self.list_dir("."))
519
        while queue:
520
            relpath = queue.pop(0)
521
            st = self.stat(relpath)
522
            if stat.S_ISDIR(st.st_mode):
523
                for i, basename in enumerate(self.list_dir(relpath)):
5193.4.5 by Mattias Eriksson
pep8
524
                    queue.insert(i, relpath + "/" + basename)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
525
            else:
526
                yield relpath
527
528
    def stat(self, relpath):
529
        """Return the stat information for a file."""
530
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
531
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
532
                mutter("GIO stat: %s", relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
533
            f = self._get_GIO(relpath)
534
            return GioStatResult(f)
535
        except gio.Error, e:
536
            self._translate_gio_error(e, relpath, extra='error w/ stat')
537
538
    def lock_read(self, relpath):
539
        """Lock the given file for shared (read) access.
540
        :return: A lock object, which should be passed to Transport.unlock()
541
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
542
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
543
            mutter("GIO lock_read", relpath)
5193.4.5 by Mattias Eriksson
pep8
544
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
545
        class BogusLock(object):
5193.4.5 by Mattias Eriksson
pep8
546
            # The old RemoteBranch ignore lock for reading, so we will
547
            # continue that tradition and return a bogus lock object.
548
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
549
            def __init__(self, path):
550
                self.path = path
5193.4.5 by Mattias Eriksson
pep8
551
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
552
            def unlock(self):
553
                pass
5193.4.5 by Mattias Eriksson
pep8
554
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
555
        return BogusLock(relpath)
556
557
    def lock_write(self, relpath):
558
        """Lock the given file for exclusive (write) access.
559
        WARNING: many transports do not support this, so trying avoid using it
560
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
561
        :return: A lock object, whichshould be passed to Transport.unlock()
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
562
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
563
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
564
            mutter("GIO lock_write", relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
565
        return self.lock_read(relpath)
566
567
    def _translate_gio_error(self, err, path, extra=None):
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
568
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
569
            mutter("GIO Error: %s %s" % (str(err), path))
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
570
        if extra is None:
571
            extra = str(err)
572
        if err.code == gio.ERROR_NOT_FOUND:
573
            raise errors.NoSuchFile(path, extra=extra)
574
        elif err.code == gio.ERROR_EXISTS:
575
            raise errors.FileExists(path, extra=extra)
576
        elif err.code == gio.ERROR_NOT_DIRECTORY:
577
            raise errors.NotADirectory(path, extra=extra)
578
        elif err.code == gio.ERROR_NOT_EMPTY:
579
            raise errors.DirectoryNotEmpty(path, extra=extra)
580
        elif err.code == gio.ERROR_BUSY:
581
            raise errors.ResourceBusy(path, extra=extra)
582
        elif err.code == gio.ERROR_PERMISSION_DENIED:
583
            raise errors.PermissionDenied(path, extra=extra)
5193.4.22 by Mattias Eriksson
Handle gio.Error on mount done and specifically handle gio.ERROR_HOST_NOT_FOUND
584
        elif err.code == gio.ERROR_HOST_NOT_FOUND:
585
            raise errors.PathError(path, extra=extra)
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
586
        elif err.code == gio.ERROR_IS_DIRECTORY:
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
587
            raise errors.PathError(path, extra=extra)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
588
        else:
589
            mutter('unable to understand error for path: %s: %s', path, err)
5193.4.20 by Mattias Eriksson
Remove not needed \ when breaking lines
590
            raise errors.PathError(path,
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
591
                    extra="Unhandled gio error: " + str(err))
592
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
593
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
594
def get_test_permutations():
595
    """Return the permutations to be used in testing."""
596
    from bzrlib.tests import test_server
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
597
    return [(GioTransport, GioLocalURLServer)]