~duplicity-team/duplicity/0.8-series

« back to all changes in this revision

Viewing changes to duplicity/collections.py

  • Committer: kenneth at loafman
  • Date: 2018-07-23 16:32:30 UTC
  • Revision ID: kenneth@loafman.com-20180723163230-i226wdy5q2zzgfc7
* Fixed unadorned strings to unicode in duplicity/*/*
  - Some fixup due to shifting indenataion not matching PEP8.
  - Substituted for non-ascii char in jottlibbackend.py comment.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
# along with duplicity; if not, write to the Free Software Foundation,
20
20
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
21
 
22
 
"""Classes and functions on collections of backup volumes"""
 
22
u"""Classes and functions on collections of backup volumes"""
23
23
 
24
24
from future_builtins import filter, map
25
25
 
49
49
 
50
50
 
51
51
class BackupSet:
52
 
    """
 
52
    u"""
53
53
    Backup set - the backup information produced by one session
54
54
    """
55
55
    def __init__(self, backend, action):
56
 
        """
 
56
        u"""
57
57
        Initialize new backup set, only backend is required at first
58
58
        """
59
59
        self.backend = backend
70
70
        self.action = action
71
71
 
72
72
    def is_complete(self):
73
 
        """
 
73
        u"""
74
74
        Assume complete if found manifest file
75
75
        """
76
76
        return self.remote_manifest_name
77
77
 
78
78
    def add_filename(self, filename):
79
 
        """
 
79
        u"""
80
80
        Add a filename to given set.  Return true if it fits.
81
81
 
82
82
        The filename will match the given set if it has the right
87
87
        @type filename: string
88
88
        """
89
89
        pr = file_naming.parse(filename)
90
 
        if not pr or not (pr.type == "full" or pr.type == "inc"):
 
90
        if not pr or not (pr.type == u"full" or pr.type == u"inc"):
91
91
            return False
92
92
 
93
93
        if not self.info_set:
115
115
        return True
116
116
 
117
117
    def set_info(self, pr):
118
 
        """
 
118
        u"""
119
119
        Set BackupSet information from ParseResults object
120
120
 
121
121
        @param pr: parse results
136
136
        self.files_changed = mf.get_files_changed()
137
137
 
138
138
    def set_manifest(self, remote_filename):
139
 
        """
 
139
        u"""
140
140
        Add local and remote manifest filenames to backup set
141
141
        """
142
142
        assert not self.remote_manifest_name, (self.remote_manifest_name,
143
143
                                               remote_filename)
144
144
        self.remote_manifest_name = remote_filename
145
145
 
146
 
        if self.action not in ["collection-status", "replicate"]:
 
146
        if self.action not in [u"collection-status", u"replicate"]:
147
147
            local_filename_list = globals.archive_dir_path.listdir()
148
148
        else:
149
149
            local_filename_list = []
160
160
                break
161
161
 
162
162
    def delete(self):
163
 
        """
 
163
        u"""
164
164
        Remove all files in set, both local and remote
165
165
        """
166
166
        rfn = self.get_filenames()
168
168
        try:
169
169
            self.backend.delete(rfn)
170
170
        except Exception:
171
 
            log.Debug(_("BackupSet.delete: missing %s") % [util.fsdecode(f) for f in rfn])
 
171
            log.Debug(_(u"BackupSet.delete: missing %s") % [util.fsdecode(f) for f in rfn])
172
172
            pass
173
 
        if self.action not in ["collection-status", "replicate"]:
 
173
        if self.action not in [u"collection-status", u"replicate"]:
174
174
            local_filename_list = globals.archive_dir_path.listdir()
175
175
        else:
176
176
            local_filename_list = []
182
182
                try:
183
183
                    globals.archive_dir_path.append(lfn).delete()
184
184
                except Exception:
185
 
                    log.Debug(_("BackupSet.delete: missing %s") % [util.fsdecode(f) for f in lfn])
 
185
                    log.Debug(_(u"BackupSet.delete: missing %s") % [util.fsdecode(f) for f in lfn])
186
186
                    pass
187
187
        util.release_lockfile()
188
188
 
189
189
    def __unicode__(self):
190
 
        """
 
190
        u"""
191
191
        For now just list files in set
192
192
        """
193
193
        filelist = []
197
197
        return u"[%s]" % u", ".join(map(util.fsdecode, filelist))
198
198
 
199
199
    def get_timestr(self):
200
 
        """
 
200
        u"""
201
201
        Return time string suitable for log statements
202
202
        """
203
203
        return dup_time.timetopretty(self.time or self.end_time)
204
204
 
205
205
    def check_manifests(self, check_remote=True):
206
 
        """
 
206
        u"""
207
207
        Make sure remote manifest is equal to local one
208
208
        """
209
209
        if not self.remote_manifest_name and not self.local_manifest_path:
210
 
            log.FatalError(_("Fatal Error: No manifests found for most recent backup"),
 
210
            log.FatalError(_(u"Fatal Error: No manifests found for most recent backup"),
211
211
                           log.ErrorCode.no_manifests)
212
 
        assert self.remote_manifest_name, "if only one, should be remote"
 
212
        assert self.remote_manifest_name, u"if only one, should be remote"
213
213
 
214
214
        remote_manifest = self.get_remote_manifest() if check_remote else None
215
215
        if self.local_manifest_path:
216
216
            local_manifest = self.get_local_manifest()
217
217
        if remote_manifest and self.local_manifest_path and local_manifest:
218
218
            if remote_manifest != local_manifest:
219
 
                log.FatalError(_("Fatal Error: Remote manifest does not match "
220
 
                                 "local one.  Either the remote backup set or "
221
 
                                 "the local archive directory has been corrupted."),
 
219
                log.FatalError(_(u"Fatal Error: Remote manifest does not match "
 
220
                                 u"local one.  Either the remote backup set or "
 
221
                                 u"the local archive directory has been corrupted."),
222
222
                               log.ErrorCode.mismatched_manifests)
223
223
        if not remote_manifest:
224
224
            if self.local_manifest_path:
225
225
                remote_manifest = local_manifest
226
226
            else:
227
 
                log.FatalError(_("Fatal Error: Neither remote nor local "
228
 
                                 "manifest is readable."),
 
227
                log.FatalError(_(u"Fatal Error: Neither remote nor local "
 
228
                                 u"manifest is readable."),
229
229
                               log.ErrorCode.unreadable_manifests)
230
230
        remote_manifest.check_dirinfo()
231
231
 
232
232
    def get_local_manifest(self):
233
 
        """
 
233
        u"""
234
234
        Return manifest object by reading local manifest file
235
235
        """
236
236
        assert self.local_manifest_path
237
237
        manifest_buffer = self.local_manifest_path.get_data()
238
 
        log.Info(_("Processing local manifest %s (%s)") % (
 
238
        log.Info(_(u"Processing local manifest %s (%s)") % (
239
239
            self.local_manifest_path.name, len(manifest_buffer)))
240
240
        return manifest.Manifest().from_string(manifest_buffer)
241
241
 
242
242
    def get_remote_manifest(self):
243
 
        """
 
243
        u"""
244
244
        Return manifest by reading remote manifest on backend
245
245
        """
246
246
        assert self.remote_manifest_name
247
247
        try:
248
248
            manifest_buffer = self.backend.get_data(self.remote_manifest_name)
249
249
        except GPGError as message:
250
 
            log.Error(_("Error processing remote manifest (%s): %s") %
 
250
            log.Error(_(u"Error processing remote manifest (%s): %s") %
251
251
                      (util.fsdecode(self.remote_manifest_name), util.uexc(message)))
252
252
            return None
253
 
        log.Info(_("Processing remote manifest %s (%s)") % (
 
253
        log.Info(_(u"Processing remote manifest %s (%s)") % (
254
254
            util.fsdecode(self.remote_manifest_name), len(manifest_buffer)))
255
255
        return manifest.Manifest().from_string(manifest_buffer)
256
256
 
257
257
    def get_manifest(self):
258
 
        """
 
258
        u"""
259
259
        Return manifest object, showing preference for local copy
260
260
        """
261
261
        if self.local_manifest_path:
264
264
            return self.get_remote_manifest()
265
265
 
266
266
    def get_filenames(self):
267
 
        """
 
267
        u"""
268
268
        Return sorted list of (remote) filenames of files in set
269
269
        """
270
270
        assert self.info_set
282
282
        return volume_filenames
283
283
 
284
284
    def get_time(self):
285
 
        """
 
285
        u"""
286
286
        Return time if full backup, or end_time if incremental
287
287
        """
288
288
        if self.time:
289
289
            return self.time
290
290
        if self.end_time:
291
291
            return self.end_time
292
 
        assert 0, "Neither self.time nor self.end_time set"
 
292
        assert 0, u"Neither self.time nor self.end_time set"
293
293
 
294
294
    def get_files_changed(self):
295
295
        return self.files_changed
296
296
 
297
297
    def __len__(self):
298
 
        """
 
298
        u"""
299
299
        Return the number of volumes in the set
300
300
        """
301
301
        return len(self.volume_name_dict.keys())
302
302
 
303
303
    def __eq__(self, other):
304
 
        """
 
304
        u"""
305
305
        Return whether this backup set is equal to other
306
306
        """
307
307
        return self.type == other.type and \
312
312
 
313
313
 
314
314
class BackupChain:
315
 
    """
 
315
    u"""
316
316
    BackupChain - a number of linked BackupSets
317
317
 
318
318
    A BackupChain always starts with a full backup set and continues
319
319
    with incremental ones.
320
320
    """
321
321
    def __init__(self, backend):
322
 
        """
 
322
        u"""
323
323
        Initialize new chain, only backend is required at first
324
324
        """
325
325
        self.backend = backend
328
328
        self.start_time, self.end_time = None, None
329
329
 
330
330
    def set_full(self, fullset):
331
 
        """
 
331
        u"""
332
332
        Add full backup set
333
333
        """
334
334
        assert not self.fullset and isinstance(fullset, BackupSet)
337
337
        self.start_time, self.end_time = fullset.time, fullset.time
338
338
 
339
339
    def add_inc(self, incset):
340
 
        """
 
340
        u"""
341
341
        Add incset to self.  Return False if incset does not match
342
342
        """
343
343
        if self.end_time == incset.start_time:
346
346
            if (self.incset_list and
347
347
                    incset.start_time == self.incset_list[-1].start_time and
348
348
                    incset.end_time > self.incset_list[-1]):
349
 
                log.Info(_("Preferring Backupset over previous one!"))
 
349
                log.Info(_(u"Preferring Backupset over previous one!"))
350
350
                self.incset_list[-1] = incset
351
351
            else:
352
 
                log.Info(_("Ignoring incremental Backupset (start_time: %s; needed: %s)") %
 
352
                log.Info(_(u"Ignoring incremental Backupset (start_time: %s; needed: %s)") %
353
353
                         (dup_time.timetopretty(incset.start_time),
354
354
                          dup_time.timetopretty(self.end_time)))
355
355
                return False
356
356
        self.end_time = incset.end_time
357
 
        log.Info(_("Added incremental Backupset (start_time: %s / end_time: %s)") %
 
357
        log.Info(_(u"Added incremental Backupset (start_time: %s / end_time: %s)") %
358
358
                 (dup_time.timetopretty(incset.start_time),
359
359
                  dup_time.timetopretty(incset.end_time)))
360
360
        assert self.end_time
361
361
        return True
362
362
 
363
363
    def delete(self, keep_full=False):
364
 
        """
 
364
        u"""
365
365
        Delete all sets in chain, in reverse order
366
366
        """
367
367
        for i in range(len(self.incset_list) - 1, -1, -1):
370
370
            self.fullset.delete()
371
371
 
372
372
    def get_sets_at_time(self, time):
373
 
        """
 
373
        u"""
374
374
        Return a list of sets in chain earlier or equal to time
375
375
        """
376
376
        older_incsets = [s for s in self.incset_list if s.end_time <= time]
377
377
        return [self.fullset] + older_incsets
378
378
 
379
379
    def get_last(self):
380
 
        """
 
380
        u"""
381
381
        Return last BackupSet in chain
382
382
        """
383
383
        if self.incset_list:
386
386
            return self.fullset
387
387
 
388
388
    def get_first(self):
389
 
        """
 
389
        u"""
390
390
        Return first BackupSet in chain (ie the full backup)
391
391
        """
392
392
        return self.fullset
393
393
 
394
394
    def short_desc(self):
395
 
        """
 
395
        u"""
396
396
        Return a short one-line description of the chain,
397
397
        suitable for log messages.
398
398
        """
399
 
        return "[%s]-[%s]" % (dup_time.timetopretty(self.start_time),
400
 
                              dup_time.timetopretty(self.end_time))
 
399
        return u"[%s]-[%s]" % (dup_time.timetopretty(self.start_time),
 
400
                               dup_time.timetopretty(self.end_time))
401
401
 
402
 
    def to_log_info(self, prefix=''):
403
 
        """
 
402
    def to_log_info(self, prefix=u''):
 
403
        u"""
404
404
        Return summary, suitable for printing to log
405
405
        """
406
406
        l = []
407
407
        for s in self.get_all_sets():
408
408
            if s.time:
409
 
                type = "full"
 
409
                type = u"full"
410
410
                time = s.time
411
411
            else:
412
 
                type = "inc"
 
412
                type = u"inc"
413
413
                time = s.end_time
414
414
            if s.encrypted:
415
 
                enc = "enc"
 
415
                enc = u"enc"
416
416
            else:
417
 
                enc = "noenc"
418
 
            l.append("%s%s %s %d %s" % (prefix, type, dup_time.timetostring(time), (len(s)), enc))
 
417
                enc = u"noenc"
 
418
            l.append(u"%s%s %s %d %s" % (prefix, type, dup_time.timetostring(time), (len(s)), enc))
419
419
        return l
420
420
 
421
421
    def __str__(self):
422
 
        """
 
422
        u"""
423
423
        Return string representation, for testing purposes
424
424
        """
425
 
        set_schema = "%20s   %30s   %15s"
426
 
        l = ["-------------------------",
427
 
             _("Chain start time: ") + dup_time.timetopretty(self.start_time),
428
 
             _("Chain end time: ") + dup_time.timetopretty(self.end_time),
429
 
             _("Number of contained backup sets: %d") %
 
425
        set_schema = u"%20s   %30s   %15s"
 
426
        l = [u"-------------------------",
 
427
             _(u"Chain start time: ") + dup_time.timetopretty(self.start_time),
 
428
             _(u"Chain end time: ") + dup_time.timetopretty(self.end_time),
 
429
             _(u"Number of contained backup sets: %d") %
430
430
             (len(self.incset_list) + 1,),
431
 
             _("Total number of contained volumes: %d") %
 
431
             _(u"Total number of contained volumes: %d") %
432
432
             (self.get_num_volumes(),),
433
 
             set_schema % (_("Type of backup set:"), _("Time:"), _("Num volumes:"))]
 
433
             set_schema % (_(u"Type of backup set:"), _(u"Time:"), _(u"Num volumes:"))]
434
434
 
435
435
        for s in self.get_all_sets():
436
436
            if s.time:
437
 
                type = _("Full")
 
437
                type = _(u"Full")
438
438
                time = s.time
439
439
            else:
440
 
                type = _("Incremental")
 
440
                type = _(u"Incremental")
441
441
                time = s.end_time
442
442
            l.append(set_schema % (type, dup_time.timetopretty(time), len(s)))
443
443
 
444
 
        l.append("-------------------------")
445
 
        return "\n".join(l)
 
444
        l.append(u"-------------------------")
 
445
        return u"\n".join(l)
446
446
 
447
447
    def get_num_volumes(self):
448
 
        """
 
448
        u"""
449
449
        Return the total number of volumes in the chain
450
450
        """
451
451
        n = 0
454
454
        return n
455
455
 
456
456
    def get_all_sets(self):
457
 
        """
 
457
        u"""
458
458
        Return list of all backup sets in chain
459
459
        """
460
460
        if self.fullset:
464
464
 
465
465
 
466
466
class SignatureChain:
467
 
    """
 
467
    u"""
468
468
    A number of linked SignatureSets
469
469
 
470
470
    Analog to BackupChain - start with a full-sig, and continue with
471
471
    new-sigs.
472
472
    """
473
473
    def __init__(self, local, location):
474
 
        """
 
474
        u"""
475
475
        Return new SignatureChain.
476
476
 
477
477
        local should be true iff the signature chain resides in
493
493
        self.start_time, self.end_time = None, None
494
494
 
495
495
    def __str__(self):
496
 
        """
 
496
        u"""
497
497
        Local or Remote and List of files in the set
498
498
        """
499
499
        if self.archive_dir_path:
500
 
            place = _("local")
 
500
            place = _(u"local")
501
501
        else:
502
 
            place = _("remote")
 
502
            place = _(u"remote")
503
503
        filelist = []
504
504
        if self.fullsig:
505
505
            filelist.append(self.fullsig)
506
506
        filelist.extend(self.inclist)
507
 
        return "%s: [%s]" % (place, ", ".join(filelist))
 
507
        return u"%s: [%s]" % (place, u", ".join(filelist))
508
508
 
509
509
    def check_times(self, time_list):
510
 
        """
 
510
        u"""
511
511
        Check to make sure times are in whole seconds
512
512
        """
513
513
        for time in time_list:
514
514
            if type(time) not in integer_types:
515
 
                assert 0, "Time %s in %s wrong type" % (time, time_list)
 
515
                assert 0, u"Time %s in %s wrong type" % (time, time_list)
516
516
 
517
517
    def islocal(self):
518
 
        """
 
518
        u"""
519
519
        Return true if represents a signature chain in archive_dir_path
520
520
        """
521
521
        if self.archive_dir_path:
524
524
            return False
525
525
 
526
526
    def add_filename(self, filename, pr=None):
527
 
        """
 
527
        u"""
528
528
        Add new sig filename to current chain.  Return true if fits
529
529
        """
530
530
        if not pr:
533
533
            return None
534
534
 
535
535
        if self.fullsig:
536
 
            if pr.type != "new-sig":
 
536
            if pr.type != u"new-sig":
537
537
                return None
538
538
            if pr.start_time != self.end_time:
539
539
                return None
542
542
            self.end_time = pr.end_time
543
543
            return 1
544
544
        else:
545
 
            if pr.type != "full-sig":
 
545
            if pr.type != u"full-sig":
546
546
                return None
547
547
            self.fullsig = filename
548
548
            self.check_times([pr.time, pr.time])
550
550
            return 1
551
551
 
552
552
    def get_fileobjs(self, time=None):
553
 
        """
 
553
        u"""
554
554
        Return ordered list of signature fileobjs opened for reading,
555
555
        optionally at a certain time
556
556
        """
557
557
        assert self.fullsig
558
558
        if self.archive_dir_path:  # local
559
559
            def filename_to_fileobj(filename):
560
 
                """Open filename in archive_dir_path, return filtered fileobj"""
 
560
                u"""Open filename in archive_dir_path, return filtered fileobj"""
561
561
                sig_dp = path.DupPath(self.archive_dir_path.name, (filename,))
562
 
                return sig_dp.filtered_open("rb")
 
562
                return sig_dp.filtered_open(u"rb")
563
563
        else:
564
564
            filename_to_fileobj = self.backend.get_fileobj_read
565
565
        return [filename_to_fileobj(f) for f in self.get_filenames(time)]
566
566
 
567
567
    def delete(self, keep_full=False):
568
 
        """
 
568
        u"""
569
569
        Remove all files in signature set
570
570
        """
571
571
        # Try to delete in opposite order, so something useful even if aborted
583
583
            self.backend.delete(inclist_copy)
584
584
 
585
585
    def get_filenames(self, time=None):
586
 
        """
 
586
        u"""
587
587
        Return ordered list of filenames in set, up to a provided time
588
588
        """
589
589
        if self.fullsig:
601
601
 
602
602
 
603
603
class CollectionsStatus:
604
 
    """
 
604
    u"""
605
605
    Hold information about available chains and sets
606
606
    """
607
607
    def __init__(self, backend, archive_dir_path, action):
608
 
        """
 
608
        u"""
609
609
        Make new object.  Does not set values
610
610
        """
611
611
        self.backend = backend
631
631
        self.values_set = None
632
632
 
633
633
    def to_log_info(self):
634
 
        """
 
634
        u"""
635
635
        Return summary of the collection, suitable for printing to log
636
636
        """
637
 
        l = ["backend %s" % (self.backend.__class__.__name__,),
638
 
             "archive-dir %s" % (self.archive_dir_path,)]
 
637
        l = [u"backend %s" % (self.backend.__class__.__name__,),
 
638
             u"archive-dir %s" % (self.archive_dir_path,)]
639
639
 
640
640
        for i in range(len(self.other_backup_chains)):
641
641
            # A bit of a misnomer.  Chain might have a sig.
642
 
            l.append("chain-no-sig %d" % (i,))
643
 
            l += self.other_backup_chains[i].to_log_info(' ')
 
642
            l.append(u"chain-no-sig %d" % (i,))
 
643
            l += self.other_backup_chains[i].to_log_info(u' ')
644
644
 
645
645
        if self.matched_chain_pair:
646
 
            l.append("chain-complete")
647
 
            l += self.matched_chain_pair[1].to_log_info(' ')
 
646
            l.append(u"chain-complete")
 
647
            l += self.matched_chain_pair[1].to_log_info(u' ')
648
648
 
649
 
        l.append("orphaned-sets-num %d" % (len(self.orphaned_backup_sets),))
650
 
        l.append("incomplete-sets-num %d" % (len(self.incomplete_backup_sets),))
 
649
        l.append(u"orphaned-sets-num %d" % (len(self.orphaned_backup_sets),))
 
650
        l.append(u"incomplete-sets-num %d" % (len(self.incomplete_backup_sets),))
651
651
 
652
652
        return l
653
653
 
654
654
    def __unicode__(self):
655
 
        """
 
655
        u"""
656
656
        Return string summary of the collection
657
657
        """
658
 
        l = [_("Collection Status"),
 
658
        l = [_(u"Collection Status"),
659
659
             u"-----------------",
660
 
             _("Connecting with backend: %s") %
 
660
             _(u"Connecting with backend: %s") %
661
661
             (self.backend.__class__.__name__,),
662
 
             _("Archive dir: %s") % (self.archive_dir_path.uc_name if self.archive_dir_path else 'None',)]
 
662
             _(u"Archive dir: %s") % (self.archive_dir_path.uc_name if self.archive_dir_path else u'None',)]
663
663
 
664
 
        l.append("\n" +
665
 
                 ngettext("Found %d secondary backup chain.",
666
 
                          "Found %d secondary backup chains.",
 
664
        l.append(u"\n" +
 
665
                 ngettext(u"Found %d secondary backup chain.",
 
666
                          u"Found %d secondary backup chains.",
667
667
                          len(self.other_backup_chains))
668
668
                 % len(self.other_backup_chains))
669
669
        for i in range(len(self.other_backup_chains)):
670
 
            l.append(_("Secondary chain %d of %d:") %
 
670
            l.append(_(u"Secondary chain %d of %d:") %
671
671
                     (i + 1, len(self.other_backup_chains)))
672
672
            l.append(unicode(self.other_backup_chains[i]))
673
 
            l.append("")
 
673
            l.append(u"")
674
674
 
675
675
        if self.matched_chain_pair:
676
 
            l.append("\n" + _("Found primary backup chain with matching "
677
 
                     "signature chain:"))
 
676
            l.append(u"\n" + _(u"Found primary backup chain with matching "
 
677
                     u"signature chain:"))
678
678
            l.append(unicode(self.matched_chain_pair[1]))
679
679
        else:
680
 
            l.append(_("No backup chains with active signatures found"))
 
680
            l.append(_(u"No backup chains with active signatures found"))
681
681
 
682
682
        if self.orphaned_backup_sets or self.incomplete_backup_sets:
683
 
            l.append(ngettext("Also found %d backup set not part of any chain,",
684
 
                              "Also found %d backup sets not part of any chain,",
 
683
            l.append(ngettext(u"Also found %d backup set not part of any chain,",
 
684
                              u"Also found %d backup sets not part of any chain,",
685
685
                              len(self.orphaned_backup_sets))
686
686
                     % (len(self.orphaned_backup_sets),))
687
 
            l.append(ngettext("and %d incomplete backup set.",
688
 
                              "and %d incomplete backup sets.",
 
687
            l.append(ngettext(u"and %d incomplete backup set.",
 
688
                              u"and %d incomplete backup sets.",
689
689
                              len(self.incomplete_backup_sets))
690
690
                     % (len(self.incomplete_backup_sets),))
691
691
            # TRANSL: "cleanup" is a hard-coded command, so do not translate it
692
 
            l.append(_('These may be deleted by running duplicity with the '
693
 
                       '"cleanup" command.'))
 
692
            l.append(_(u'These may be deleted by running duplicity with the '
 
693
                       u'"cleanup" command.'))
694
694
        else:
695
 
            l.append(_("No orphaned or incomplete backup sets found."))
 
695
            l.append(_(u"No orphaned or incomplete backup sets found."))
696
696
 
697
697
        return u"\n".join(l)
698
698
 
699
699
    def set_values(self, sig_chain_warning=1):
700
 
        """
 
700
        u"""
701
701
        Set values from archive_dir_path and backend.
702
702
 
703
703
        Returns self for convenience.  If sig_chain_warning is set to None,
708
708
 
709
709
        # get remote filename list
710
710
        backend_filename_list = self.backend.list()
711
 
        log.Debug(ngettext("%d file exists on backend",
712
 
                           "%d files exist on backend",
 
711
        log.Debug(ngettext(u"%d file exists on backend",
 
712
                           u"%d files exist on backend",
713
713
                           len(backend_filename_list)) %
714
714
                  len(backend_filename_list))
715
715
 
716
716
        # get local filename list
717
 
        if self.action not in ["collection-status", "replicate"]:
 
717
        if self.action not in [u"collection-status", u"replicate"]:
718
718
            local_filename_list = self.archive_dir_path.listdir()
719
719
        else:
720
720
            local_filename_list = []
721
 
        log.Debug(ngettext("%d file exists in cache",
722
 
                           "%d files exist in cache",
 
721
        log.Debug(ngettext(u"%d file exists in cache",
 
722
                           u"%d files exist in cache",
723
723
                           len(local_filename_list)) %
724
724
                  len(local_filename_list))
725
725
 
738
738
        self.all_backup_chains = backup_chains
739
739
 
740
740
        assert len(backup_chains) == len(self.all_backup_chains), \
741
 
            "get_sorted_chains() did something more than re-ordering"
 
741
            u"get_sorted_chains() did something more than re-ordering"
742
742
 
743
743
        local_sig_chains, self.local_orphaned_sig_names = \
744
744
            self.get_signature_chains(True)
750
750
        return self
751
751
 
752
752
    def set_matched_chain_pair(self, sig_chains, backup_chains):
753
 
        """
 
753
        u"""
754
754
        Set self.matched_chain_pair and self.other_sig/backup_chains
755
755
 
756
756
        The latest matched_chain_pair will be set.  If there are both
771
771
                elif (len(latest_backup_chain.get_all_sets()) >= 2 and
772
772
                      sig_chains[i].end_time == latest_backup_chain.get_all_sets()[-2].end_time):
773
773
                    # It matches, remove the last backup set:
774
 
                    log.Warn(_("Warning, discarding last backup set, because "
775
 
                               "of missing signature file."))
 
774
                    log.Warn(_(u"Warning, discarding last backup set, because "
 
775
                               u"of missing signature file."))
776
776
                    self.incomplete_backup_sets.append(latest_backup_chain.incset_list[-1])
777
777
                    latest_backup_chain.incset_list = latest_backup_chain.incset_list[:-1]
778
778
                else:
788
788
            self.other_backup_chains.remove(self.matched_chain_pair[1])
789
789
 
790
790
    def warn(self, sig_chain_warning):
791
 
        """
 
791
        u"""
792
792
        Log various error messages if find incomplete/orphaned files
793
793
        """
794
794
        assert self.values_set
795
795
 
796
796
        if self.local_orphaned_sig_names:
797
 
            log.Warn(ngettext("Warning, found the following local orphaned "
798
 
                              "signature file:",
799
 
                              "Warning, found the following local orphaned "
800
 
                              "signature files:",
 
797
            log.Warn(ngettext(u"Warning, found the following local orphaned "
 
798
                              u"signature file:",
 
799
                              u"Warning, found the following local orphaned "
 
800
                              u"signature files:",
801
801
                              len(self.local_orphaned_sig_names)) + u"\n" +
802
802
                     u"\n".join(map(util.fsdecode, self.local_orphaned_sig_names)),
803
803
                     log.WarningCode.orphaned_sig)
804
804
 
805
805
        if self.remote_orphaned_sig_names:
806
 
            log.Warn(ngettext("Warning, found the following remote orphaned "
807
 
                              "signature file:",
808
 
                              "Warning, found the following remote orphaned "
809
 
                              "signature files:",
 
806
            log.Warn(ngettext(u"Warning, found the following remote orphaned "
 
807
                              u"signature file:",
 
808
                              u"Warning, found the following remote orphaned "
 
809
                              u"signature files:",
810
810
                              len(self.remote_orphaned_sig_names)) + u"\n" +
811
811
                     u"\n".join(map(util.fsdecode, self.remote_orphaned_sig_names)),
812
812
                     log.WarningCode.orphaned_sig)
813
813
 
814
814
        if self.all_sig_chains and sig_chain_warning and not self.matched_chain_pair:
815
 
            log.Warn(_("Warning, found signatures but no corresponding "
816
 
                       "backup files"), log.WarningCode.unmatched_sig)
 
815
            log.Warn(_(u"Warning, found signatures but no corresponding "
 
816
                       u"backup files"), log.WarningCode.unmatched_sig)
817
817
 
818
818
        if self.incomplete_backup_sets:
819
 
            log.Warn(_("Warning, found incomplete backup sets, probably left "
820
 
                       "from aborted session"), log.WarningCode.incomplete_backup)
 
819
            log.Warn(_(u"Warning, found incomplete backup sets, probably left "
 
820
                       u"from aborted session"), log.WarningCode.incomplete_backup)
821
821
 
822
822
        if self.orphaned_backup_sets:
823
 
            log.Warn(ngettext("Warning, found the following orphaned "
824
 
                              "backup file:",
825
 
                              "Warning, found the following orphaned "
826
 
                              "backup files:",
 
823
            log.Warn(ngettext(u"Warning, found the following orphaned "
 
824
                              u"backup file:",
 
825
                              u"Warning, found the following orphaned "
 
826
                              u"backup files:",
827
827
                              len(self.orphaned_backup_sets)) + u"\n" +
828
828
                     u"\n".join(map(unicode, self.orphaned_backup_sets)),
829
829
                     log.WarningCode.orphaned_backup)
830
830
 
831
831
    def get_backup_chains(self, filename_list):
832
 
        """
 
832
        u"""
833
833
        Split given filename_list into chains
834
834
 
835
835
        Return value will be tuple (list of chains, list of sets, list
837
837
        not fitting into any chain, and the incomplete sets are sets
838
838
        missing files.
839
839
        """
840
 
        log.Debug(_("Extracting backup chains from list of files: %s")
 
840
        log.Debug(_(u"Extracting backup chains from list of files: %s")
841
841
                  % [util.fsdecode(f) for f in filename_list])
842
842
        # First put filenames in set form
843
843
        sets = []
844
844
 
845
845
        def add_to_sets(filename):
846
 
            """
 
846
            u"""
847
847
            Try adding filename to existing sets, or make new one
848
848
            """
849
849
            for set in sets:
850
850
                if set.add_filename(filename):
851
 
                    log.Debug(_("File %s is part of known set") % (util.fsdecode(filename),))
 
851
                    log.Debug(_(u"File %s is part of known set") % (util.fsdecode(filename),))
852
852
                    break
853
853
            else:
854
 
                log.Debug(_("File %s is not part of a known set; creating new set") % (util.fsdecode(filename),))
 
854
                log.Debug(_(u"File %s is not part of a known set; creating new set") % (util.fsdecode(filename),))
855
855
                new_set = BackupSet(self.backend, self.action)
856
856
                if new_set.add_filename(filename):
857
857
                    sets.append(new_set)
858
858
                else:
859
 
                    log.Debug(_("Ignoring file (rejected by backup set) '%s'") % util.fsdecode(filename))
 
859
                    log.Debug(_(u"Ignoring file (rejected by backup set) '%s'") % util.fsdecode(filename))
860
860
 
861
861
        for f in filename_list:
862
862
            add_to_sets(f)
865
865
        chains, orphaned_sets = [], []
866
866
 
867
867
        def add_to_chains(set):
868
 
            """
 
868
            u"""
869
869
            Try adding set to existing chains, or make new one
870
870
            """
871
 
            if set.type == "full":
 
871
            if set.type == u"full":
872
872
                new_chain = BackupChain(self.backend)
873
873
                new_chain.set_full(set)
874
874
                chains.append(new_chain)
875
 
                log.Debug(_("Found backup chain %s") % (new_chain.short_desc()))
 
875
                log.Debug(_(u"Found backup chain %s") % (new_chain.short_desc()))
876
876
            else:
877
 
                assert set.type == "inc"
 
877
                assert set.type == u"inc"
878
878
                for chain in chains:
879
879
                    if chain.add_inc(set):
880
 
                        log.Debug(_("Added set %s to pre-existing chain %s") % (set.get_timestr(),
881
 
                                                                                chain.short_desc()))
 
880
                        log.Debug(_(u"Added set %s to pre-existing chain %s") % (set.get_timestr(),
 
881
                                                                                 chain.short_desc()))
882
882
                        break
883
883
                else:
884
 
                    log.Debug(_("Found orphaned set %s") % (set.get_timestr(),))
 
884
                    log.Debug(_(u"Found orphaned set %s") % (set.get_timestr(),))
885
885
                    orphaned_sets.append(set)
886
886
        for s in sets:
887
887
            add_to_chains(s)
888
888
        return (chains, orphaned_sets, incomplete_sets)
889
889
 
890
890
    def get_sorted_sets(self, set_list):
891
 
        """
 
891
        u"""
892
892
        Sort set list by end time, return (sorted list, incomplete)
893
893
        """
894
894
        time_set_pairs, incomplete_sets = [], []
895
895
        for set in set_list:
896
896
            if not set.is_complete():
897
897
                incomplete_sets.append(set)
898
 
            elif set.type == "full":
 
898
            elif set.type == u"full":
899
899
                time_set_pairs.append((set.time, set))
900
900
            else:
901
901
                time_set_pairs.append((set.end_time, set))
903
903
        return ([p[1] for p in time_set_pairs], incomplete_sets)
904
904
 
905
905
    def get_signature_chains(self, local, filelist=None):
906
 
        """
 
906
        u"""
907
907
        Find chains in archive_dir_path (if local is true) or backend
908
908
 
909
909
        Use filelist if given, otherwise regenerate.  Return value is
914
914
            if filelist is not None:
915
915
                return filelist
916
916
            elif local:
917
 
                if self.action not in ["collection-status", "replicate"]:
 
917
                if self.action not in [u"collection-status", u"replicate"]:
918
918
                    return self.archive_dir_path.listdir()
919
919
                else:
920
920
                    return []
922
922
                return self.backend.list()
923
923
 
924
924
        def get_new_sigchain():
925
 
            """
 
925
            u"""
926
926
            Return new empty signature chain
927
927
            """
928
928
            if local:
935
935
        for filename in get_filelist():
936
936
            pr = file_naming.parse(filename)
937
937
            if pr:
938
 
                if pr.type == "full-sig":
 
938
                if pr.type == u"full-sig":
939
939
                    new_chain = get_new_sigchain()
940
940
                    assert new_chain.add_filename(filename, pr)
941
941
                    chains.append(new_chain)
942
 
                elif pr.type == "new-sig":
 
942
                elif pr.type == u"new-sig":
943
943
                    new_sig_filenames.append(filename)
944
944
 
945
945
        # compare by file time
958
958
        return (chains, orphaned_filenames)
959
959
 
960
960
    def get_sorted_chains(self, chain_list):
961
 
        """
 
961
        u"""
962
962
        Return chains sorted by end_time.  If tie, local goes last
963
963
        """
964
964
        # Build dictionary from end_times to lists of corresponding chains
989
989
        return sorted_chain_list
990
990
 
991
991
    def get_backup_chain_at_time(self, time):
992
 
        """
 
992
        u"""
993
993
        Return backup chain covering specified time
994
994
 
995
995
        Tries to find the backup chain covering the given time.  If
997
997
        that, the earliest chain.
998
998
        """
999
999
        if not self.all_backup_chains:
1000
 
            raise CollectionsError("No backup chains found")
 
1000
            raise CollectionsError(u"No backup chains found")
1001
1001
 
1002
1002
        covering_chains = [c for c in self.all_backup_chains
1003
1003
                           if c.start_time <= time <= c.end_time]
1004
1004
        if len(covering_chains) > 1:
1005
 
            raise CollectionsError("Two chains cover the given time")
 
1005
            raise CollectionsError(u"Two chains cover the given time")
1006
1006
        elif len(covering_chains) == 1:
1007
1007
            return covering_chains[0]
1008
1008
 
1013
1013
            return self.all_backup_chains[0]  # no chains are old enough
1014
1014
 
1015
1015
    def get_signature_chain_at_time(self, time):
1016
 
        """
 
1016
        u"""
1017
1017
        Return signature chain covering specified time
1018
1018
 
1019
1019
        Tries to find the signature chain covering the given time.  If
1021
1021
        that, the earliest chain.
1022
1022
        """
1023
1023
        if not self.all_sig_chains:
1024
 
            raise CollectionsError("No signature chains found")
 
1024
            raise CollectionsError(u"No signature chains found")
1025
1025
 
1026
1026
        covering_chains = [c for c in self.all_sig_chains
1027
1027
                           if c.start_time <= time <= c.end_time]
1035
1035
            # no chains are old enough, give oldest and warn user
1036
1036
            oldest = self.all_sig_chains[0]
1037
1037
            if time < oldest.start_time:
1038
 
                log.Warn(_("No signature chain for the requested time. "
1039
 
                           "Using oldest available chain, starting at time %s.") %
 
1038
                log.Warn(_(u"No signature chain for the requested time. "
 
1039
                           u"Using oldest available chain, starting at time %s.") %
1040
1040
                         dup_time.timetopretty(oldest.start_time),
1041
1041
                         log.WarningCode.no_sig_for_time,
1042
1042
                         dup_time.timetostring(oldest.start_time))
1043
1043
            return oldest
1044
1044
 
1045
1045
    def get_extraneous(self, extra_clean):
1046
 
        """
 
1046
        u"""
1047
1047
        Return list of the names of extraneous duplicity files
1048
1048
 
1049
1049
        A duplicity file is considered extraneous if it is
1073
1073
        return local_filenames, remote_filenames
1074
1074
 
1075
1075
    def sort_sets(self, setlist):
1076
 
        """Return new list containing same elems of setlist, sorted by time"""
 
1076
        u"""Return new list containing same elems of setlist, sorted by time"""
1077
1077
        pairs = [(s.get_time(), s) for s in setlist]
1078
1078
        pairs.sort()
1079
1079
        return [p[1] for p in pairs]
1080
1080
 
1081
1081
    def get_chains_older_than(self, t):
1082
 
        """
 
1082
        u"""
1083
1083
        Returns a list of backup chains older than the given time t
1084
1084
 
1085
1085
        All of the times will be associated with an intact chain.
1099
1099
        return old_chains
1100
1100
 
1101
1101
    def get_signature_chains_older_than(self, t):
1102
 
        """
 
1102
        u"""
1103
1103
        Returns a list of signature chains older than the given time t
1104
1104
 
1105
1105
        All of the times will be associated with an intact chain.
1119
1119
        return old_chains
1120
1120
 
1121
1121
    def get_last_full_backup_time(self):
1122
 
        """
 
1122
        u"""
1123
1123
        Return the time of the last full backup,
1124
1124
        or 0 if there is none.
1125
1125
        """
1126
1126
        return self.get_nth_last_full_backup_time(1)
1127
1127
 
1128
1128
    def get_nth_last_full_backup_time(self, n):
1129
 
        """
 
1129
        u"""
1130
1130
        Return the time of the nth to last full backup,
1131
1131
        or 0 if there is none.
1132
1132
        """
1137
1137
            return chain.get_first().time
1138
1138
 
1139
1139
    def get_last_backup_chain(self):
1140
 
        """
 
1140
        u"""
1141
1141
        Return the last full backup of the collection,
1142
1142
        or None if there is no full backup chain.
1143
1143
        """
1144
1144
        return self.get_nth_last_backup_chain(1)
1145
1145
 
1146
1146
    def get_nth_last_backup_chain(self, n):
1147
 
        """
 
1147
        u"""
1148
1148
        Return the nth-to-last full backup of the collection,
1149
1149
        or None if there is less than n backup chains.
1150
1150
 
1168
1168
        return sorted[n - 1]
1169
1169
 
1170
1170
    def get_older_than(self, t):
1171
 
        """
 
1171
        u"""
1172
1172
        Returns a list of backup sets older than the given time t
1173
1173
 
1174
1174
        All of the times will be associated with an intact chain.
1183
1183
        return self.sort_sets(old_sets)
1184
1184
 
1185
1185
    def get_older_than_required(self, t):
1186
 
        """
 
1186
        u"""
1187
1187
        Returns list of old backup sets required by new sets
1188
1188
 
1189
1189
        This function is similar to the previous one, but it only
1199
1199
        return self.sort_sets(result_sets)
1200
1200
 
1201
1201
    def get_file_changed_record(self, filepath):
1202
 
        """
 
1202
        u"""
1203
1203
        Returns time line of specified file changed
1204
1204
        """
1205
1205
        # quick fix to spaces in filepath
1206
1206
        modified_filepath = filepath
1207
 
        if " " in filepath:
1208
 
            modified_filepath = '"' + filepath.replace(" ", r"\x20") + '"'
 
1207
        if u" " in filepath:
 
1208
            modified_filepath = u'"' + filepath.replace(u" ", r"\x20") + u'"'
1209
1209
 
1210
1210
        if not self.matched_chain_pair:
1211
 
            return ""
 
1211
            return u""
1212
1212
 
1213
1213
        all_backup_set = self.matched_chain_pair[1].get_all_sets()
1214
1214
        specified_file_backup_set = []
1230
1230
        self.fileinfo_list = fileinfo_list
1231
1231
 
1232
1232
    def __unicode__(self):
1233
 
        set_schema = "%20s   %30s  %20s"
1234
 
        l = ["-------------------------",
1235
 
             _("File: %s") % (self.filepath),
1236
 
             _("Total number of backup: %d") % len(self.fileinfo_list),
1237
 
             set_schema % (_("Type of backup set:"), _("Time:"), _("Type of file change:"))]
 
1233
        set_schema = u"%20s   %30s  %20s"
 
1234
        l = [u"-------------------------",
 
1235
             _(u"File: %s") % (self.filepath),
 
1236
             _(u"Total number of backup: %d") % len(self.fileinfo_list),
 
1237
             set_schema % (_(u"Type of backup set:"), _(u"Time:"), _(u"Type of file change:"))]
1238
1238
 
1239
1239
        for s in self.fileinfo_list:
1240
1240
            backup_type = s[0]
1241
1241
            backup_set = s[1]
1242
1242
            if backup_set.time:
1243
 
                type = _("Full")
 
1243
                type = _(u"Full")
1244
1244
            else:
1245
 
                type = _("Incremental")
 
1245
                type = _(u"Incremental")
1246
1246
            l.append(set_schema % (type, dup_time.timetopretty(backup_set.get_time()), backup_type.title()))
1247
1247
 
1248
 
        l.append("-------------------------")
1249
 
        return "\n".join(l)
 
1248
        l.append(u"-------------------------")
 
1249
        return u"\n".join(l)