~ubuntu-branches/ubuntu/maverick/pybackpack/maverick

« back to all changes in this revision

Viewing changes to pybackpack/actions.py

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Price
  • Date: 2009-12-03 23:42:03 UTC
  • mfrom: (1.1.5 upstream) (4.1.4 sid)
  • Revision ID: james.westby@ubuntu.com-20091203234203-sx75310vwaalckit
Tags: 0.5.7-1
* New upstream release
* Update to standards version 3.8.3
  - No changes required
* debian/control:
  - Mention rdiff-backup in the package description (Closes: #554818)
  - Build-dep on python instead of python-dev which isn't needed
  - Depend on python-brasero instead of python-nautilusburn
    (Closes: #544625)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os
 
2
import sys
 
3
import gtk
 
4
import shutil
 
5
import mkisofs
 
6
import version
 
7
import traceback
 
8
import subprocess
 
9
import rdiff_interface
 
10
import gobject
 
11
from LogHandler import LogHandler
 
12
try:
 
13
        import braseroburn
 
14
        braseroburn.start()
 
15
except ImportError:
 
16
        pass
 
17
 
 
18
 
 
19
class DestinationError(Exception):
 
20
        FAIL_MISC = 1
 
21
        FAIL_RD = 2  # Read perm on destination
 
22
        FAIL_WR = 4  # Write perm on destination
 
23
        FAIL_EX = 8  # Execute perm on destination
 
24
        FAIL_HM = 16 # Destination is homedir
 
25
        FAIL_MK = 32 # Could not make destination directory
 
26
        WARN_NEMPTY = 64 # Not empty
 
27
 
 
28
        msgs = {
 
29
                FAIL_MISC: '',
 
30
                FAIL_RD: _("No read permission on destination directory"),
 
31
                FAIL_WR: _("No write permission on destination directory"),
 
32
                FAIL_EX: _("No execute permission on destionation directory"),
 
33
                FAIL_HM: _("Destination is home directory. This would remove the files in your home directory. Please choose a different location."),
 
34
                FAIL_MK: _("Could not create destination directory"),
 
35
                WARN_NEMPTY: _("Destination directory is not empty"),
 
36
        }
 
37
 
 
38
        """
 
39
        An exception to be raised when the backup destination is unsuitable.
 
40
        """
 
41
        def __init__(self, code, path, msg=None):
 
42
                Exception.__init__(self)
 
43
                if msg is None:
 
44
                        self.message = "%s: %s" % (self.msgs[code], path)
 
45
                else:
 
46
                        self.message = "%s: %s (%s)" % (self.msgs[code], path, msg)
 
47
                self.code = code
 
48
        
 
49
        def __str__(self):
 
50
                return self.message
 
51
 
 
52
class BackupError(Exception):
 
53
        """
 
54
        An exception to be raised when something goes wrong during a backup.
 
55
        """
 
56
        pass
 
57
 
 
58
class Backup:
 
59
        """
 
60
        The base class which provides local backup functionality.
 
61
        """
 
62
        def __init__(self):
 
63
                """
 
64
                Initialise a Backup object
 
65
                """
 
66
                self.progress = -1
 
67
                self.progress_cbs = []
 
68
                self.destination = None
 
69
                self.filecount = 0
 
70
                self.force = False
 
71
 
 
72
        def set_destination(self, destination):
 
73
                """
 
74
                Set the destination directory for the backup.
 
75
                """
 
76
                if os.path.abspath(destination) == os.path.abspath(os.environ['HOME']):
 
77
                        self.destination = None
 
78
                        raise DestinationError(DestinationError.FAIL_HM,
 
79
                                        destination)
 
80
                self.destination = destination
 
81
 
 
82
        def add_progress_cb(self, progress_cb, data=None):
 
83
                """
 
84
                Set the signal handler for reporting backup progress.
 
85
                If data is provided, it will be passed to the handler.
 
86
                """
 
87
                self.progress_cbs.append((progress_cb,data))
 
88
        
 
89
        def report_progress(self, status):
 
90
                """
 
91
                Report progress percentage to the progress callbacks.
 
92
                """
 
93
                for cb, data in self.progress_cbs:
 
94
                        if callable(cb):
 
95
                                cb(self.progress, status, data)
 
96
        
 
97
        def check_destination(self):
 
98
                """
 
99
                Check the destination directory is OK to back up to.
 
100
                """
 
101
                if not os.path.exists(self.destination):
 
102
                        try:
 
103
                                self.report_progress(
 
104
                                        _("Creating destination directory"))
 
105
                                os.makedirs(self.destination)
 
106
                        except OSError, e:
 
107
                                raise DestinationError(DestinationError.FAIL_MK,
 
108
                                                self.destination, e.strerror)
 
109
 
 
110
                home = os.environ['HOME']
 
111
                if os.path.abspath(self.destination) == os.path.abspath(home):
 
112
                        raise DestinationError(DestinationError.FAIL_HM,
 
113
                                        self.destination)
 
114
                if not os.access(self.destination, os.R_OK):
 
115
                        raise DestinationError(DestinationError.FAIL_RD,
 
116
                                        self.destination)
 
117
                if not os.access(self.destination, os.W_OK):
 
118
                        raise DestinationError(DestinationError.FAIL_WR,
 
119
                                        self.destination)
 
120
                if not os.access(self.destination, os.X_OK):
 
121
                        raise DestinationError(DestinationError.FAIL_EX,
 
122
                                        self.destination)
 
123
 
 
124
                ls = os.listdir(self.destination)
 
125
                if len(ls) > 0:
 
126
                        if ls.count('rdiff-backup-data') == 0:
 
127
                                raise DestinationError(
 
128
                                        DestinationError.WARN_NEMPTY, self.destination)
 
129
 
 
130
 
 
131
        def __analyze_source(self, bset, ui):
 
132
                """
 
133
                Check files are ok to backup and count them.
 
134
                """
 
135
                count = 0
 
136
                self.report_progress(_("Analyzing source files"))
 
137
 
 
138
                for path in bset.files_include:
 
139
                        if path == bset.dest:
 
140
                                ui.statuswin.addmsg(_("Destination directory in backup source. Omitting."))
 
141
                        elif os.path.isdir(path):
 
142
                                for dirname, dirs, files in os.walk(path):
 
143
                                        for f in files:
 
144
                                                fullpath = os.path.join(dirname, f)
 
145
                                                if fullpath in bset.files_exclude:
 
146
                                                        continue
 
147
                                                if fullpath == bset.dest:
 
148
                                                        ui.statuswin.addmsg(_("NOTE: Destination directory inside backup set. Omitting."))
 
149
                                                elif os.path.islink(fullpath):
 
150
                                                        count += 1
 
151
                                                elif os.access(fullpath, os.R_OK):
 
152
                                                        count += 1
 
153
                                        for d in dirs:
 
154
                                                fullpath = os.path.join(dirname, d)
 
155
                                                if fullpath in bset.files_exclude:
 
156
                                                        dirs.remove(d)
 
157
                                                        continue
 
158
                                                if os.access(fullpath, os.R_OK|os.X_OK):
 
159
                                                        count += 1
 
160
                                        self.report_progress("%s: %d" % (_("Files found"), count))
 
161
                        elif os.access(path, os.R_OK):
 
162
                                count += 1
 
163
 
 
164
                self.report_progress(_("Backup source analysis complete."))
 
165
                return count
 
166
 
 
167
        def do_backup(self, backupset, ui, remote=False):
 
168
                """
 
169
                Run a local backup of the given backup set.
 
170
                """
 
171
                filecount = self.__analyze_source(backupset, ui)
 
172
                self.report_progress(_("Running backup"))
 
173
                stdout = LogHandler(ui.statuswin, ui.widgets, filecount, True)
 
174
                stderr = LogHandler(ui.statuswin)
 
175
                self.progress = 0
 
176
                self.report_progress(_("Creating backup"))
 
177
 
 
178
                try:
 
179
                        rdiff_interface.BackupSet(
 
180
                                        backupset,
 
181
                                        self.destination,
 
182
                                        stdout,
 
183
                                        stderr,
 
184
                                        copy_set_file = not remote,
 
185
                                        is_ssh = remote,
 
186
                                        force = self.force)
 
187
                except rdiff_interface.RdiffError, e:
 
188
                        if e.status != 0:
 
189
                                raise BackupError(_("Backup failed: %s") % str(e))
 
190
                except:
 
191
                        raise
 
192
                else:
 
193
                        self.progress = 100
 
194
                        self.report_progress(_("Backup complete"))
 
195
                finally:
 
196
                        sys.stdout = sys.__stdout__
 
197
                        sys.stderr = sys.__stderr__
 
198
 
 
199
 
 
200
 
 
201
class CDBackup(Backup):
 
202
        """
 
203
        Provides methods for backing up files to a CD or DVD.
 
204
        """
 
205
        def __init__(self, tmpdir='/tmp'):
 
206
                """
 
207
                Initialise a CDBackup object.
 
208
                """
 
209
                Backup.__init__(self)
 
210
                self.destination = os.path.join(tmpdir, "%s.cdimage" %
 
211
                                version.APPPATH)
 
212
                self.isopath = os.path.join(tmpdir, "%s.iso" % version.APPPATH)
 
213
                gobject.threads_init()
 
214
 
 
215
        def check_destination(self):
 
216
                """
 
217
                Clear the temporary directory before checking it.
 
218
                """
 
219
                try:
 
220
                        shutil.rmtree(self.destination)
 
221
                except:
 
222
                        pass # Didn't exist in the first place
 
223
 
 
224
                Backup.check_destination(self)
 
225
        
 
226
        def show_iso_progress(self, progress):
 
227
                """
 
228
                Helper function for reporting iso image creation progress.
 
229
                """
 
230
                self.progress = progress
 
231
                self.report_progress(_("Creating CD image"))
 
232
 
 
233
        def create_iso(self, backupset, ui):
 
234
                """
 
235
                Create an iso image using the Mkisofs class.
 
236
                """
 
237
                self.report_progress(_("Creating temporary backup"))
 
238
                self.do_backup(backupset, ui)
 
239
                isomaker = mkisofs.Mkisofs()
 
240
                isomaker.set_progress_hook(self.show_iso_progress)
 
241
                isomaker.create_iso(self.destination, self.isopath)
 
242
                isoret = isomaker.get_retval()
 
243
                if isoret != 0:
 
244
                        errmsg = os.strerror(isoret)
 
245
                        msg = _("Backup failed; could not create CD image %(filename)s: %(error)s\n") %\
 
246
                                        {'filename':self.isopath, 'error':errmsg}
 
247
                        raise BackupError(msg)
 
248
        
 
249
        def burn_iso(self, drive, cd_progress_cb):
 
250
                """
 
251
                Burn the iso image to CD/DVD.
 
252
                """
 
253
                self.report_progress(_("Writing image to CD/DVD"))
 
254
 
 
255
                track = braseroburn.TrackImage()
 
256
                track.set_source(self.isopath)
 
257
 
 
258
                session = braseroburn.SessionCfg()
 
259
                session.add_track(track)
 
260
                session.set_burner(drive)
 
261
 
 
262
                options = braseroburn.BurnOptions(session)
 
263
                err = options.run()
 
264
                options.destroy()
 
265
                if err != gtk.RESPONSE_OK:
 
266
                        self.progress = 0
 
267
                        self.report_progress("")
 
268
                        raise BackupError(_("An error occurred while burning the CD."))
 
269
                        
 
270
                cdburner = braseroburn.BurnDialog()
 
271
                cdburner.show()
 
272
                if not cdburner.run(session):
 
273
                        cdburner.destroy()
 
274
                        self.progress = 0
 
275
                        self.report_progress("")
 
276
                        raise BackupError(_("An error occurred while burning the CD."))
 
277
 
 
278
                cdburner.destroy()
 
279
                
 
280
                self.report_progress(_("Cleaning up temporary files"))
 
281
                try:
 
282
                        shutil.rmtree(dest_path)
 
283
                        os.unlink(self.isopath)
 
284
                except:
 
285
                        pass
 
286
 
 
287
                self.progress = 100
 
288
                self.report_progress(_("Backup complete"))
 
289
 
 
290
class RemoteBackup(Backup):
 
291
        """
 
292
        Provides methods for backing up files to a remote server.
 
293
        """
 
294
 
 
295
        def set_destination(self, user, path, host):
 
296
                """
 
297
                For remote backup, the destination must include a username, a
 
298
                remote path and a hostname.
 
299
                """
 
300
                error_string = ""
 
301
                if not user:
 
302
                        error_string += _("\nUser")
 
303
                if not host:
 
304
                        error_string += _("\nHost")
 
305
                if not path:
 
306
                        error_string += _("\nPath")
 
307
                if error_string:
 
308
                        raise DestinationError(DestinationError.FAIL_MISC, "",
 
309
                                        _("Missing fields:%s") % error_string)
 
310
 
 
311
                self.destination = "%s@%s::%s" % (user, host, path)
 
312
                self.user = user
 
313
                self.host = host
 
314
                self.path = path
 
315
 
 
316
 
 
317
        def do_remote_backup(self, backupset, ui):
 
318
                """
 
319
                Do a remote backup.
 
320
                """
 
321
                self.do_backup(backupset, ui, remote = True)
 
322
                try:
 
323
                        inifile = os.path.join(rdiff_interface.setspath,
 
324
                                        backupset.path, "set.ini")
 
325
                        setfile = os.path.join(self.path, "rdiff-backup-data",
 
326
                                        "%s.set" % version.APPPATH)
 
327
                        args = "scp %s %s@%s:%s" % (inifile, self.user, self.host, setfile)
 
328
                        scp = subprocess.Popen(args, shell=True)
 
329
                        while scp.poll() is None:
 
330
                                self.report_progress(_("Transferring backup set data"))
 
331
                        if scp.poll() != 0:
 
332
                                ui.dialogs.showmsg(
 
333
_("Backup completed, but could not copy the backup set data file. You can "
 
334
  "manually copy this file from\n%(source)s\n to\n%(filepath)s (on host "
 
335
  "%(hostname)s)") % {'source':inifile, 'filepath':setfile, 'hostname':self.host})
 
336
 
 
337
                        self.progress = 100
 
338
                        self.report_progress(_("Backup completed"))
 
339
                except Exception, e:
 
340
                        raise BackupError(
 
341
                                _("An error occurred while transferring '%s'.") %\
 
342
                                inifile)
 
343