~ubuntu-branches/ubuntu/natty/duplicity/natty-proposed

« back to all changes in this revision

Viewing changes to src/backends/giobackend.py

  • Committer: Michael Terry
  • Date: 2010-01-09 03:21:49 UTC
  • mfrom: (5.2.6 squeeze)
  • Revision ID: mike@mterry.name-20100109032149-qlga2seelidmfath
* Merge from debian unstable, remaining changes: (LP: #428206)
  - 02gnupginterface.dpatch: Use system's python-gnupginterface
* fixed ssh backend failure (tried to import local pexpect module)
  (closes: #556095)
* New upstream release (closes: #539903, #420858)
* does no longer depend on python-gnupginterface: upstream
  provides a modified version which is claimed to be incompatible
* does not install a local version of python-pexpect 
  anymore (closes: #555359)
* adjusted rules to cater for future python2.6 install
  setup (closes: #547825)
* New upstream release
* lifted standards version
* New upstream release (closes: #536361, #537260, #42858, 
  #399371, #388180, #386749 )
* new project homepage
* added notes regarding changed archive-dir behaviour

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
27
 
import glib
28
 
 
29
 
import duplicity.backend
30
 
from duplicity import log
31
 
from duplicity import globals
32
 
from duplicity.errors import *
33
 
from duplicity.util import exception_traceback 
34
 
 
35
 
def ensure_dbus():
36
 
    # GIO requires a dbus session bus which can start the gvfs daemons
37
 
    # when required.  So we make sure that such a bus exists and that our
38
 
    # environment points to it.
39
 
    if 'DBUS_SESSION_BUS_ADDRESS' not in os.environ:
40
 
        output = subprocess.Popen(['dbus-launch'], stdout=subprocess.PIPE).communicate()[0]
41
 
        lines = output.split('\n')
42
 
        for line in lines:
43
 
            parts = line.split('=', 1)
44
 
            if len(parts) == 2:
45
 
                if parts[0] == 'DBUS_SESSION_BUS_PID': # cleanup at end
46
 
                    atexit.register(os.kill, int(parts[1]), signal.SIGTERM)
47
 
                os.environ[parts[0]] = parts[1]
48
 
 
49
 
class DupMountOperation(gio.MountOperation):
50
 
    """A simple MountOperation that grabs the password from the environment
51
 
       or the user.
52
 
    """
53
 
    def __init__(self, backend):
54
 
        gio.MountOperation.__init__(self)
55
 
        self.backend = backend
56
 
        self.connect('ask-password', self.ask_password)
57
 
 
58
 
    def ask_password(self, *args, **kwargs):
59
 
        self.set_password(self.backend.get_password())
60
 
        self.reply(gio.MOUNT_OPERATION_HANDLED)
61
 
 
62
 
class GIOBackend(duplicity.backend.Backend):
63
 
    """Use this backend when saving to a GIO URL.
64
 
       This is a bit of a meta-backend, in that it can handle multiple schemas.
65
 
       URLs look like schema://user@server/path.
66
 
    """
67
 
    def __init__(self, parsed_url):
68
 
        duplicity.backend.Backend.__init__(self, parsed_url)
69
 
 
70
 
        ensure_dbus()
71
 
 
72
 
        self.remote_file = gio.File(uri=parsed_url.url_string)
73
 
 
74
 
        # Now we make sure the location is mounted
75
 
        op = DupMountOperation(self)
76
 
        loop = glib.MainLoop()
77
 
        self.remote_file.mount_enclosing_volume(op, self.done_with_mount,
78
 
                                                0, user_data=loop)
79
 
        loop.run() # halt program until we're done mounting
80
 
 
81
 
    def done_with_mount(self, fileobj, result, loop):
82
 
        try:
83
 
            fileobj.mount_enclosing_volume_finish(result)
84
 
        except gio.Error, e:
85
 
            # check for NOT_SUPPORTED because some schemas (e.g. file://) validly don't
86
 
            if e.code != gio.ERROR_ALREADY_MOUNTED and e.code != gio.ERROR_NOT_SUPPORTED:
87
 
                log.FatalError(_("Connection failed, please check your password: %s")
88
 
                               % str(e), log.ErrorCode.connection_failed)
89
 
        loop.quit()
90
 
 
91
 
    def copy_progress(self, *args, **kwargs):
92
 
        pass
93
 
 
94
 
    def copy_file(self, source, target):
95
 
        for n in range(1, globals.num_retries+1):
96
 
            log.Info(_("Writing %s") % target.get_parse_name())
97
 
            try:
98
 
                source.copy(target, self.copy_progress,
99
 
                            gio.FILE_COPY_OVERWRITE | gio.FILE_COPY_NOFOLLOW_SYMLINKS)
100
 
                return
101
 
            except Exception, e:
102
 
                log.Warn("Write of '%s' failed (attempt %s): %s: %s"
103
 
                        % (target.get_parse_name(), n, e.__class__.__name__, str(e)))
104
 
                log.Debug("Backtrace of previous error: %s"
105
 
                          % exception_traceback())
106
 
        raise BackendException(_("Could not copy %s to %s") % (source.get_parse_name(),
107
 
                                                               target.get_parse_name()))
108
 
 
109
 
    def put(self, source_path, remote_filename = None):
110
 
        """Copy file to remote"""
111
 
        if not remote_filename:
112
 
            remote_filename = source_path.get_filename()
113
 
        source_file = gio.File(path=source_path.name)
114
 
        target_file = self.remote_file.get_child_for_display_name(remote_filename)
115
 
        self.copy_file(source_file, target_file)
116
 
 
117
 
    def get(self, filename, local_path):
118
 
        """Get file and put in local_path (Path object)"""
119
 
        source_file = self.remote_file.get_child_for_display_name(filename)
120
 
        target_file = gio.File(path=local_path.name)
121
 
        self.copy_file(source_file, target_file)
122
 
        local_path.setdata()
123
 
 
124
 
    def list(self):
125
 
        """List files in that directory"""
126
 
        enum = self.remote_file.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
127
 
                                                   gio.FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
128
 
        files = []
129
 
        try:
130
 
            info = enum.next_file()
131
 
            while info:
132
 
                files.append(info.get_display_name())
133
 
                info = enum.next_file()
134
 
            return files
135
 
        except Exception, e:
136
 
            raise BackendException(str(e))
137
 
 
138
 
    def delete(self, filename_list):
139
 
        """Delete all files in filename list"""
140
 
        assert type(filename_list) is not types.StringType
141
 
        try:
142
 
                for filename in filename_list:
143
 
                        self.remote_file.get_child_for_display_name(filename).delete()
144
 
        except Exception, e:
145
 
            raise BackendException(str(e))