~ubuntu-branches/ubuntu/trusty/duplicity/trusty

« back to all changes in this revision

Viewing changes to src/backends/giobackend.py

  • Committer: Package Import Robot
  • Author(s): Michael Terry
  • Date: 2011-12-06 14:15:01 UTC
  • mfrom: (1.9.4)
  • Revision ID: package-import@ubuntu.com-20111206141501-nvfaaauqivpwyb7f
Tags: 0.6.17-0ubuntu1
* New upstream release
* debian/patches/06_use_passphrase.dpatch,
  debian/patches/07_large_rackspace_list.dpatch,
  debian/patches/08_check_volumes.dpatch:
  - Dropped, applied upstream
* debian/rules:
  - Run new upstream test suite during build
* debian/control:
  - Add rdiff as a build-dep to run above test suite
* debian/patches/06testfixes.dpatch:
  - Fix a few tests to not fail erroneously
* debian/patches/07fixincresume.dpatch:
  - Fix a bug with resuming an incremental backup that would result in
    a bogus error.  Also patches in a test for it.
* debian/tests/full-cycle-local:
  - New DEP-8 test script that backs up locally, restores, and checks files
* debian/tests/full-cycle-u1:
  - New DEP-8 test script that does the same as above, but to Ubuntu One
* debian/tests/control:
  - Start of DEP-8 test suite.  Only enable above full-cycle-local test
    for automatic execution.  The other is for manual testing right now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2
 
#
3
 
# Copyright 2009 Michael Terry <mike@mterry.name>
4
 
#
5
 
# This file is part of duplicity.
6
 
#
7
 
# Duplicity is free software; you can redistribute it and/or modify it
8
 
# under the terms of the GNU General Public License as published by the
9
 
# Free Software Foundation; either version 2 of the License, or (at your
10
 
# option) any later version.
11
 
#
12
 
# Duplicity is distributed in the hope that it will be useful, but
13
 
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 
# General Public License for more details.
16
 
#
17
 
# You should have received a copy of the GNU General Public License
18
 
# along with duplicity; if not, write to the Free Software Foundation,
19
 
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
 
 
21
 
import os
22
 
import types
23
 
import subprocess
24
 
import atexit
25
 
import signal
26
 
import gio #@UnresolvedImport
27
 
import glib #@UnresolvedImport
28
 
 
29
 
import duplicity.backend
30
 
from duplicity.backend import retry
31
 
from duplicity import log
32
 
from duplicity import globals
33
 
from duplicity import util
34
 
from duplicity.errors import * #@UnusedWildImport
35
 
from duplicity.util import exception_traceback
36
 
 
37
 
def ensure_dbus():
38
 
    # GIO requires a dbus session bus which can start the gvfs daemons
39
 
    # when required.  So we make sure that such a bus exists and that our
40
 
    # environment points to it.
41
 
    if 'DBUS_SESSION_BUS_ADDRESS' not in os.environ:
42
 
        output = subprocess.Popen(['dbus-launch'], stdout=subprocess.PIPE).communicate()[0]
43
 
        lines = output.split('\n')
44
 
        for line in lines:
45
 
            parts = line.split('=', 1)
46
 
            if len(parts) == 2:
47
 
                if parts[0] == 'DBUS_SESSION_BUS_PID': # cleanup at end
48
 
                    atexit.register(os.kill, int(parts[1]), signal.SIGTERM)
49
 
                os.environ[parts[0]] = parts[1]
50
 
 
51
 
class DupMountOperation(gio.MountOperation):
52
 
    """A simple MountOperation that grabs the password from the environment
53
 
       or the user.
54
 
    """
55
 
    def __init__(self, backend):
56
 
        gio.MountOperation.__init__(self)
57
 
        self.backend = backend
58
 
        self.connect('ask-password', self.ask_password)
59
 
 
60
 
    def ask_password(self, *args, **kwargs):
61
 
        self.set_password(self.backend.get_password())
62
 
        self.reply(gio.MOUNT_OPERATION_HANDLED)
63
 
 
64
 
class GIOBackend(duplicity.backend.Backend):
65
 
    """Use this backend when saving to a GIO URL.
66
 
       This is a bit of a meta-backend, in that it can handle multiple schemas.
67
 
       URLs look like schema://user@server/path.
68
 
    """
69
 
    def __init__(self, parsed_url):
70
 
        duplicity.backend.Backend.__init__(self, parsed_url)
71
 
 
72
 
        ensure_dbus()
73
 
 
74
 
        self.remote_file = gio.File(uri=parsed_url.url_string)
75
 
 
76
 
        # Now we make sure the location is mounted
77
 
        op = DupMountOperation(self)
78
 
        loop = glib.MainLoop()
79
 
        self.remote_file.mount_enclosing_volume(op, self.done_with_mount,
80
 
                                                0, user_data=loop)
81
 
        loop.run() # halt program until we're done mounting
82
 
 
83
 
    def done_with_mount(self, fileobj, result, loop):
84
 
        try:
85
 
            fileobj.mount_enclosing_volume_finish(result)
86
 
        except gio.Error, e:
87
 
            # check for NOT_SUPPORTED because some schemas (e.g. file://) validly don't
88
 
            if e.code != gio.ERROR_ALREADY_MOUNTED and e.code != gio.ERROR_NOT_SUPPORTED:
89
 
                log.FatalError(_("Connection failed, please check your password: %s")
90
 
                               % str(e), log.ErrorCode.connection_failed)
91
 
        loop.quit()
92
 
 
93
 
    def handle_error(self, raise_error, e, op, file1=None, file2=None):
94
 
        if raise_error:
95
 
            raise e
96
 
        code = log.ErrorCode.backend_error
97
 
        if isinstance(e, gio.Error):
98
 
            if e.code == gio.ERROR_PERMISSION_DENIED:
99
 
                code = log.ErrorCode.backend_permission_denied
100
 
            elif e.code == gio.ERROR_NOT_FOUND:
101
 
                code = log.ErrorCode.backend_not_found
102
 
            elif e.code == gio.ERROR_NO_SPACE:
103
 
                code = log.ErrorCode.backend_no_space
104
 
        extra = ' '.join([util.escape(x) for x in [file1, file2] if x])
105
 
        extra = ' '.join([op, extra])
106
 
        log.FatalError(str(e), code, extra)
107
 
 
108
 
    def copy_progress(self, *args, **kwargs):
109
 
        pass
110
 
 
111
 
    @retry
112
 
    def copy_file(self, op, source, target, raise_errors=False):
113
 
        log.Info(_("Writing %s") % target.get_parse_name())
114
 
        try:
115
 
            source.copy(target, self.copy_progress,
116
 
                        gio.FILE_COPY_OVERWRITE | gio.FILE_COPY_NOFOLLOW_SYMLINKS)
117
 
        except Exception, e:
118
 
            self.handle_error(raise_errors, e, op, source.get_parse_name(),
119
 
                              target.get_parse_name())
120
 
 
121
 
    def put(self, source_path, remote_filename = None):
122
 
        """Copy file to remote"""
123
 
        if not remote_filename:
124
 
            remote_filename = source_path.get_filename()
125
 
        source_file = gio.File(path=source_path.name)
126
 
        target_file = self.remote_file.get_child(remote_filename)
127
 
        self.copy_file('put', source_file, target_file)
128
 
 
129
 
    def get(self, filename, local_path):
130
 
        """Get file and put in local_path (Path object)"""
131
 
        source_file = self.remote_file.get_child(filename)
132
 
        target_file = gio.File(path=local_path.name)
133
 
        self.copy_file('get', source_file, target_file)
134
 
        local_path.setdata()
135
 
 
136
 
    @retry
137
 
    def list(self, raise_errors=False):
138
 
        """List files in that directory"""
139
 
        files = []
140
 
        try:
141
 
            enum = self.remote_file.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_NAME,
142
 
                                                       gio.FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
143
 
            info = enum.next_file()
144
 
            while info:
145
 
                files.append(info.get_name())
146
 
                info = enum.next_file()
147
 
        except Exception, e:
148
 
            self.handle_error(raise_errors, e, 'list',
149
 
                              self.remote_file.get_parse_name())
150
 
        return files
151
 
 
152
 
    @retry
153
 
    def delete(self, filename_list, raise_errors=False):
154
 
        """Delete all files in filename list"""
155
 
        assert type(filename_list) is not types.StringType
156
 
        for filename in filename_list:
157
 
            target_file = self.remote_file.get_child(filename)
158
 
            try:
159
 
                target_file.delete()
160
 
            except Exception, e:
161
 
                if isinstance(e, gio.Error):
162
 
                    if e.code == gio.ERROR_NOT_FOUND:
163
 
                        continue
164
 
                self.handle_error(raise_errors, e, 'delete',
165
 
                                  target_file.get_parse_name())
166
 
                return