~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
6027.1.13 by Vincent Ladeuil
Remove deprecated code
274
    def get(self, relpath, 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
        """
284
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
285
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
286
                mutter("GIO get: %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
287
            f = self._get_GIO(relpath)
288
            fin = f.read()
289
            buf = fin.read()
290
            fin.close()
291
            ret = StringIO(buf)
292
            return ret
293
        except gio.Error, e:
5193.4.9 by Mattias Eriksson
Cleaned up some user messages
294
            #If we get a not mounted here it might mean
295
            #that a bad path has been entered (or that mount failed)
296
            if (e.code == gio.ERROR_NOT_MOUNTED):
5193.4.20 by Mattias Eriksson
Remove not needed \ when breaking lines
297
                raise errors.PathError(relpath,
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
298
                  extra='Failed to get file, make sure the path is correct. ' \
299
                  + str(e))
5193.4.9 by Mattias Eriksson
Cleaned up some user messages
300
            else:
301
                self._translate_gio_error(e, relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
302
303
    def put_file(self, relpath, fp, mode=None):
304
        """Copy the file-like object into the location.
305
306
        :param relpath: Location to put the contents, relative to base.
307
        :param fp:       File-like or string object.
308
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
309
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
310
            mutter("GIO put_file %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
311
        tmppath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
5193.4.5 by Mattias Eriksson
pep8
312
                    os.getpid(), random.randint(0, 0x7FFFFFFF))
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
313
        f = None
314
        fout = None
315
        try:
5268.1.1 by Vincent Ladeuil
Fix gio-related test failures.
316
            closed = True
5261.1.1 by Robert Collins
Python 2.4 friendliness is important to bzr.
317
            try:
318
                f = self._get_GIO(tmppath)
319
                fout = f.create()
320
                closed = False
321
                length = self._pump(fp, fout)
322
                fout.close()
323
                closed = True
324
                self.stat(tmppath)
325
                dest = self._get_GIO(relpath)
326
                f.move(dest, flags=gio.FILE_COPY_OVERWRITE)
327
                f = None
328
                if mode is not None:
329
                    self._setmode(relpath, mode)
330
                return length
331
            except gio.Error, e:
332
                self._translate_gio_error(e, relpath)
5193.4.14 by Mattias Eriksson
Cleanup exception handling in put_file
333
        finally:
334
            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
335
                fout.close()
5193.4.14 by Mattias Eriksson
Cleanup exception handling in put_file
336
            if f is not None and f.query_exists():
337
                f.delete()
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
338
339
    def mkdir(self, relpath, mode=None):
340
        """Create a directory at the given path."""
341
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
342
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
343
                mutter("GIO mkdir: %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
344
            f = self._get_GIO(relpath)
345
            f.make_directory()
346
            self._setmode(relpath, mode)
347
        except gio.Error, e:
348
            self._translate_gio_error(e, relpath)
349
350
    def open_write_stream(self, relpath, mode=None):
351
        """See Transport.open_write_stream."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
352
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
353
            mutter("GIO open_write_stream %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
354
        if mode is not None:
355
            self._setmode(relpath, mode)
356
        result = GioFileStream(self, relpath)
357
        _file_streams[self.abspath(relpath)] = result
358
        return result
359
360
    def recommended_page_size(self):
361
        """See Transport.recommended_page_size().
362
363
        For FTP we suggest a large page size to reduce the overhead
364
        introduced by latency.
365
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
366
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
367
            mutter("GIO recommended_page")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
368
        return 64 * 1024
369
370
    def rmdir(self, relpath):
371
        """Delete the directory at rel_path"""
372
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
373
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
374
                mutter("GIO rmdir %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
375
            st = self.stat(relpath)
376
            if stat.S_ISDIR(st.st_mode):
377
                f = self._get_GIO(relpath)
378
                f.delete()
379
            else:
380
                raise errors.NotADirectory(relpath)
381
        except gio.Error, e:
382
            self._translate_gio_error(e, relpath)
383
        except errors.NotADirectory, e:
384
            #just pass it forward
385
            raise e
386
        except Exception, e:
5193.4.5 by Mattias Eriksson
pep8
387
            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
388
            raise errors.PathError(relpath)
389
390
    def append_file(self, relpath, file, mode=None):
391
        """Append the text in the file-like object into the final
392
        location.
393
        """
394
        #GIO append_to seems not to append but to truncate
395
        #Work around this.
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
396
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
397
            mutter("GIO append_file: %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
398
        tmppath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
5193.4.5 by Mattias Eriksson
pep8
399
                    os.getpid(), random.randint(0, 0x7FFFFFFF))
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
400
        try:
401
            result = 0
402
            fo = self._get_GIO(tmppath)
403
            fi = self._get_GIO(relpath)
404
            fout = fo.create()
5193.4.5 by Mattias Eriksson
pep8
405
            try:
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
406
                info = GioStatResult(fi)
407
                result = info.st_size
408
                fin = fi.read()
5193.4.15 by Mattias Eriksson
Make append_file more clear by adding comment explaining things
409
                self._pump(fin, fout)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
410
                fin.close()
5193.4.19 by Mattias Eriksson
Explicit check for glib and gio and raise DependencyNotPresent
411
            #This separate except is to catch and ignore the
412
            #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
413
            #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
414
            #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
415
            except gio.Error, e:
416
                if e.code != gio.ERROR_NOT_FOUND:
417
                    self._translate_gio_error(e, relpath)
5193.4.5 by Mattias Eriksson
pep8
418
            length = self._pump(file, fout)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
419
            fout.close()
5193.4.16 by Mattias Eriksson
Improved append safety by moving size check before the move
420
            info = GioStatResult(fo)
5193.4.5 by Mattias Eriksson
pep8
421
            if info.st_size != result + length:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
422
                raise errors.BzrError("Failed to append size after " \
423
                      "(%d) is not original (%d) + written (%d) total (%d)" % \
424
                      (info.st_size, result, length, result + length))
5193.4.16 by Mattias Eriksson
Improved append safety by moving size check before the move
425
            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
426
            return result
427
        except gio.Error, e:
428
            self._translate_gio_error(e, relpath)
429
430
    def _setmode(self, relpath, mode):
431
        """Set permissions on a path.
432
433
        Only set permissions on Unix systems
434
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
435
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
436
            mutter("GIO _setmode %s" % relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
437
        if mode:
438
            try:
439
                f = self._get_GIO(relpath)
5193.4.5 by Mattias Eriksson
pep8
440
                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
441
            except gio.Error, e:
442
                if e.code == gio.ERROR_NOT_SUPPORTED:
443
                    # Command probably not available on this server
444
                    mutter("GIO Could not set permissions to %s on %s. %s",
445
                        oct(mode), self._remote_path(relpath), str(e))
446
                else:
447
                    self._translate_gio_error(e, relpath)
448
449
    def rename(self, rel_from, rel_to):
450
        """Rename without special overwriting"""
451
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
452
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
453
                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
454
            f = self._get_GIO(rel_from)
455
            t = self._get_GIO(rel_to)
456
            f.move(t)
457
        except gio.Error, e:
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
458
            self._translate_gio_error(e, rel_from)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
459
460
    def move(self, rel_from, rel_to):
461
        """Move the item at rel_from to the location at rel_to"""
462
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
463
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
464
                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
465
            f = self._get_GIO(rel_from)
466
            t = self._get_GIO(rel_to)
467
            f.move(t, flags=gio.FILE_COPY_OVERWRITE)
468
        except gio.Error, e:
469
            self._translate_gio_error(e, relfrom)
470
471
    def delete(self, relpath):
472
        """Delete the item at relpath"""
473
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
474
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
475
                mutter("GIO delete: %s", relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
476
            f = self._get_GIO(relpath)
477
            f.delete()
478
        except gio.Error, e:
479
            self._translate_gio_error(e, relpath)
480
481
    def external_url(self):
482
        """See bzrlib.transport.Transport.external_url."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
483
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
484
            mutter("GIO external_url", self.base)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
485
        # GIO external url
486
        return self.base
487
488
    def listable(self):
489
        """See Transport.listable."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
490
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
491
            mutter("GIO listable")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
492
        return True
493
494
    def list_dir(self, relpath):
495
        """See Transport.list_dir."""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
496
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
497
            mutter("GIO list_dir")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
498
        try:
499
            entries = []
5193.4.5 by Mattias Eriksson
pep8
500
            f = self._get_GIO(relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
501
            children = f.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_NAME)
502
            for child in children:
503
                entries.append(urlutils.escape(child.get_name()))
504
            return entries
505
        except gio.Error, e:
506
            self._translate_gio_error(e, relpath)
507
508
    def iter_files_recursive(self):
509
        """See Transport.iter_files_recursive.
510
511
        This is cargo-culted from the SFTP transport"""
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
512
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
513
            mutter("GIO iter_files_recursive")
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
514
        queue = list(self.list_dir("."))
515
        while queue:
516
            relpath = queue.pop(0)
517
            st = self.stat(relpath)
518
            if stat.S_ISDIR(st.st_mode):
519
                for i, basename in enumerate(self.list_dir(relpath)):
5193.4.5 by Mattias Eriksson
pep8
520
                    queue.insert(i, relpath + "/" + basename)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
521
            else:
522
                yield relpath
523
524
    def stat(self, relpath):
525
        """Return the stat information for a file."""
526
        try:
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
527
            if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
528
                mutter("GIO stat: %s", relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
529
            f = self._get_GIO(relpath)
530
            return GioStatResult(f)
531
        except gio.Error, e:
532
            self._translate_gio_error(e, relpath, extra='error w/ stat')
533
534
    def lock_read(self, relpath):
535
        """Lock the given file for shared (read) access.
536
        :return: A lock object, which should be passed to Transport.unlock()
537
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
538
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
539
            mutter("GIO lock_read", relpath)
5193.4.5 by Mattias Eriksson
pep8
540
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
541
        class BogusLock(object):
5193.4.5 by Mattias Eriksson
pep8
542
            # The old RemoteBranch ignore lock for reading, so we will
543
            # continue that tradition and return a bogus lock object.
544
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
545
            def __init__(self, path):
546
                self.path = path
5193.4.5 by Mattias Eriksson
pep8
547
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
548
            def unlock(self):
549
                pass
5193.4.5 by Mattias Eriksson
pep8
550
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
551
        return BogusLock(relpath)
552
553
    def lock_write(self, relpath):
554
        """Lock the given file for exclusive (write) access.
555
        WARNING: many transports do not support this, so trying avoid using it
556
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
557
        :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
558
        """
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
559
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
560
            mutter("GIO lock_write", relpath)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
561
        return self.lock_read(relpath)
562
563
    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
564
        if 'gio' in debug.debug_flags:
5193.4.7 by Mattias Eriksson
Don't write debug output unless -Dgio is specified on the commandline
565
            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
566
        if extra is None:
567
            extra = str(err)
568
        if err.code == gio.ERROR_NOT_FOUND:
569
            raise errors.NoSuchFile(path, extra=extra)
570
        elif err.code == gio.ERROR_EXISTS:
571
            raise errors.FileExists(path, extra=extra)
572
        elif err.code == gio.ERROR_NOT_DIRECTORY:
573
            raise errors.NotADirectory(path, extra=extra)
574
        elif err.code == gio.ERROR_NOT_EMPTY:
575
            raise errors.DirectoryNotEmpty(path, extra=extra)
576
        elif err.code == gio.ERROR_BUSY:
577
            raise errors.ResourceBusy(path, extra=extra)
578
        elif err.code == gio.ERROR_PERMISSION_DENIED:
579
            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
580
        elif err.code == gio.ERROR_HOST_NOT_FOUND:
581
            raise errors.PathError(path, extra=extra)
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
582
        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
583
            raise errors.PathError(path, extra=extra)
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
584
        else:
585
            mutter('unable to understand error for path: %s: %s', path, err)
5193.4.20 by Mattias Eriksson
Remove not needed \ when breaking lines
586
            raise errors.PathError(path,
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
587
                    extra="Unhandled gio error: " + str(err))
588
5193.4.1 by Mattias Eriksson
A GIO transport based on the FTP and SFTP transport implementations
589
5193.4.10 by Mattias Eriksson
Set up the tests and add some fixes to make the pass
590
def get_test_permutations():
591
    """Return the permutations to be used in testing."""
592
    from bzrlib.tests import test_server
5193.4.13 by Mattias Eriksson
Cleanup, pep8 fixes and minor adjustments to make things clearer
593
    return [(GioTransport, GioLocalURLServer)]