197
197
return u"[%s]" % u", ".join(map(util.fsdecode, filelist))
199
199
def get_timestr(self):
201
201
Return time string suitable for log statements
203
203
return dup_time.timetopretty(self.time or self.end_time)
205
205
def check_manifests(self, check_remote=True):
207
207
Make sure remote manifest is equal to local one
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"
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
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()
232
232
def get_local_manifest(self):
234
234
Return manifest object by reading local manifest file
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)
242
242
def get_remote_manifest(self):
244
244
Return manifest by reading remote manifest on backend
246
246
assert self.remote_manifest_name
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)))
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)
257
257
def get_manifest(self):
259
259
Return manifest object, showing preference for local copy
261
261
if self.local_manifest_path:
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
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)))
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
363
363
def delete(self, keep_full=False):
365
365
Delete all sets in chain, in reverse order
367
367
for i in range(len(self.incset_list) - 1, -1, -1):
386
386
return self.fullset
388
388
def get_first(self):
390
390
Return first BackupSet in chain (ie the full backup)
392
392
return self.fullset
394
394
def short_desc(self):
396
396
Return a short one-line description of the chain,
397
397
suitable for log messages.
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))
402
def to_log_info(self, prefix=''):
402
def to_log_info(self, prefix=u''):
404
404
Return summary, suitable for printing to log
407
407
for s in self.get_all_sets():
413
413
time = s.end_time
418
l.append("%s%s %s %d %s" % (prefix, type, dup_time.timetostring(time), (len(s)), enc))
418
l.append(u"%s%s %s %d %s" % (prefix, type, dup_time.timetostring(time), (len(s)), enc))
421
421
def __str__(self):
423
423
Return string representation, for testing purposes
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:"))]
435
435
for s in self.get_all_sets():
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)))
444
l.append("-------------------------")
444
l.append(u"-------------------------")
447
447
def get_num_volumes(self):
449
449
Return the total number of volumes in the chain
493
493
self.start_time, self.end_time = None, None
495
495
def __str__(self):
497
497
Local or Remote and List of files in the set
499
499
if self.archive_dir_path:
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))
509
509
def check_times(self, time_list):
511
511
Check to make sure times are in whole seconds
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)
517
517
def islocal(self):
519
519
Return true if represents a signature chain in archive_dir_path
521
521
if self.archive_dir_path:
552
552
def get_fileobjs(self, time=None):
554
554
Return ordered list of signature fileobjs opened for reading,
555
555
optionally at a certain time
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")
564
564
filename_to_fileobj = self.backend.get_fileobj_read
565
565
return [filename_to_fileobj(f) for f in self.get_filenames(time)]
567
567
def delete(self, keep_full=False):
569
569
Remove all files in signature set
571
571
# Try to delete in opposite order, so something useful even if aborted
631
631
self.values_set = None
633
633
def to_log_info(self):
635
635
Return summary of the collection, suitable for printing to log
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,)]
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' ')
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' ')
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),))
654
654
def __unicode__(self):
656
656
Return string summary of the collection
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',)]
665
ngettext("Found %d secondary backup chain.",
666
"Found %d secondary backup chains.",
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]))
675
675
if self.matched_chain_pair:
676
l.append("\n" + _("Found primary backup chain with matching "
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]))
680
l.append(_("No backup chains with active signatures found"))
680
l.append(_(u"No backup chains with active signatures found"))
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.'))
695
l.append(_("No orphaned or incomplete backup sets found."))
695
l.append(_(u"No orphaned or incomplete backup sets found."))
697
697
return u"\n".join(l)
699
699
def set_values(self, sig_chain_warning=1):
701
701
Set values from archive_dir_path and backend.
703
703
Returns self for convenience. If sig_chain_warning is set to None,
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))
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()
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))
788
788
self.other_backup_chains.remove(self.matched_chain_pair[1])
790
790
def warn(self, sig_chain_warning):
792
792
Log various error messages if find incomplete/orphaned files
794
794
assert self.values_set
796
796
if self.local_orphaned_sig_names:
797
log.Warn(ngettext("Warning, found the following local orphaned "
799
"Warning, found the following local orphaned "
797
log.Warn(ngettext(u"Warning, found the following local orphaned "
799
u"Warning, found the following local orphaned "
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)
805
805
if self.remote_orphaned_sig_names:
806
log.Warn(ngettext("Warning, found the following remote orphaned "
808
"Warning, found the following remote orphaned "
806
log.Warn(ngettext(u"Warning, found the following remote orphaned "
808
u"Warning, found the following remote orphaned "
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)
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)
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)
822
822
if self.orphaned_backup_sets:
823
log.Warn(ngettext("Warning, found the following orphaned "
825
"Warning, found the following orphaned "
823
log.Warn(ngettext(u"Warning, found the following orphaned "
825
u"Warning, found the following orphaned "
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)
831
831
def get_backup_chains(self, filename_list):
833
833
Split given filename_list into chains
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
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
845
845
def add_to_sets(filename):
847
847
Try adding filename to existing sets, or make new one
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),))
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)
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))
861
861
for f in filename_list:
865
865
chains, orphaned_sets = [], []
867
867
def add_to_chains(set):
869
869
Try adding set to existing chains, or make new one
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()))
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(),
880
log.Debug(_(u"Added set %s to pre-existing chain %s") % (set.get_timestr(),
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)
888
888
return (chains, orphaned_sets, incomplete_sets)
890
890
def get_sorted_sets(self, set_list):
892
892
Sort set list by end time, return (sorted list, incomplete)
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))
901
901
time_set_pairs.append((set.end_time, set))
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))
1045
1045
def get_extraneous(self, extra_clean):
1047
1047
Return list of the names of extraneous duplicity files
1049
1049
A duplicity file is considered extraneous if it is
1230
1230
self.fileinfo_list = fileinfo_list
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:"))]
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:
1245
type = _("Incremental")
1245
type = _(u"Incremental")
1246
1246
l.append(set_schema % (type, dup_time.timetopretty(backup_set.get_time()), backup_type.title()))
1248
l.append("-------------------------")
1248
l.append(u"-------------------------")
1249
return u"\n".join(l)