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