# Copyright 2002 Ben Escoto
#
# This file is part of duplicity.
#
# Duplicity is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# Duplicity is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with duplicity; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Produce and parse the names of duplicity's backup files"""
import re
import dup_time, globals
full_vol_re = re.compile("^duplicity-full\\.(?P<time>.*?)\\.vol(?P<num>[0-9]+)\\.difftar($|\\.)")
full_vol_re_short = re.compile("^df\\.(?P<time>[0-9a-z]+?)\\.(?P<num>[0-9a-z]+)\\.dt($|\\.)")
full_manifest_re = re.compile("^duplicity-full\\.(?P<time>.*?)\\.manifest($|\\.)")
full_manifest_re_short = re.compile("^df\\.(?P<time>[0-9a-z]+?)\\.m($|\\.)")
inc_vol_re = re.compile("^duplicity-inc\\.(?P<start_time>.*?)\\.to\\.(?P<end_time>.*?)\\.vol(?P<num>[0-9]+)\\.difftar($|\\.)")
inc_vol_re_short = re.compile("^di\\.(?P<start_time>[0-9a-z]+?)\\.(?P<end_time>[0-9a-z]+?)\\.(?P<num>[0-9a-z]+)\\.dt($|\\.)")
inc_manifest_re = re.compile("^duplicity-inc\\.(?P<start_time>.*?)\\.to\\.(?P<end_time>.*?)\\.manifest(\\.|$)")
inc_manifest_re_short = re.compile("^di\\.(?P<start_time>[0-9a-z]+?)\\.(?P<end_time>[0-9a-z]+?)\\.m(\\.|$)")
full_sig_re = re.compile("^duplicity-full-signatures\\.(?P<time>.*?)\\.sigtar(\\.|$)")
full_sig_re_short = re.compile("^dfs\\.(?P<time>[0-9a-z]+?)\\.st(\\.|$)")
new_sig_re = re.compile("^duplicity-new-signatures\\.(?P<start_time>.*?)\\.to\\.(?P<end_time>.*?)\\.sigtar(\\.|$)")
new_sig_re_short = re.compile("^dns\\.(?P<start_time>[0-9a-z]+?)\\.(?P<end_time>[0-9a-z]+?)\\.st(\\.|$)")
def to_base36(n):
"""Return string representation of n in base 36 (use 0-9 and a-z)"""
div, mod = divmod(n, 36)
if mod <= 9: last_digit = str(mod)
else: last_digit = chr(ord('a') + mod - 10)
if n == mod: return last_digit
else: return to_base36(div)+last_digit
def from_base36(s):
"""Convert string s in base 36 to long int"""
total = 0L
for i in range(len(s)):
total *= 36
digit_ord = ord(s[i])
if ord('0') <= digit_ord <= ord('9'): total += digit_ord - ord('0')
elif ord('a') <= digit_ord <= ord('z'):
total += digit_ord - ord('a') + 10
else: assert 0, "Digit %s in %s not in proper range" % (s[i], s)
return total
def get(type, volume_number = None, manifest = None,
encrypted = None, gzipped = None):
"""Return duplicity filename of specified type
type can be "full", "inc", "full-sig", or "new-sig". volume_number
can be given with the full and inc types. If manifest is true the
filename is of a full or inc manifest file.
"""
assert dup_time.curtimestr
assert not (encrypted and gzipped)
if encrypted:
if globals.short_filenames: suffix = '.g'
else: suffix = ".gpg"
elif gzipped:
if globals.short_filenames: suffix = ".z"
else: suffix = '.gz'
else: suffix = ""
if type == "full-sig" or type == "new-sig":
assert not volume_number and not manifest
if type == "full-sig":
if globals.short_filenames:
return "dfs.%s.st%s" % (to_base36(dup_time.curtime), suffix)
else: return ("duplicity-full-signatures.%s.sigtar%s" %
(dup_time.curtimestr, suffix))
elif type == "new-sig":
if globals.short_filenames:
return "dns.%s.%s.st%s" % (to_base36(dup_time.prevtime),
to_base36(dup_time.curtime), suffix)
return "duplicity-new-signatures.%s.to.%s.sigtar%s" % \
(dup_time.prevtimestr, dup_time.curtimestr, suffix)
else:
assert volume_number or manifest
assert not (volume_number and manifest)
if volume_number:
if globals.short_filenames:
vol_string = "%s.dt" % to_base36(volume_number)
else: vol_string = "vol%d.difftar" % volume_number
else:
if globals.short_filenames: vol_string = "m"
else: vol_string = "manifest"
if type == "full":
if globals.short_filenames:
return "df.%s.%s%s" % (to_base36(dup_time.curtime),
vol_string, suffix)
else: return "duplicity-full.%s.%s%s" % (dup_time.curtimestr,
vol_string, suffix)
elif type == "inc":
if globals.short_filenames:
return "di.%s.%s.%s%s" % (to_base36(dup_time.prevtime),
to_base36(dup_time.curtime), vol_string, suffix)
else: return "duplicity-inc.%s.to.%s.%s%s" % \
(dup_time.prevtimestr, dup_time.curtimestr, vol_string, suffix)
else: assert 0
def parse(filename):
"""Parse duplicity filename, return None or ParseResults object"""
filename = filename.lower()
def str2time(timestr):
"""Return time in seconds if string can be converted, None otherwise"""
if globals.short_filenames: t = from_base36(timestr)
else:
try: t = dup_time.genstrtotime(timestr.upper())
except dup_time.TimeException: return None
return t
def get_vol_num(s):
"""Return volume number from volume number string"""
if globals.short_filenames: return from_base36(s)
else: return int(s)
def check_full():
"""Return ParseResults if file is from full backup, None otherwise"""
if globals.short_filenames: m1 = full_vol_re_short.search(filename)
else: m1 = full_vol_re.search(filename)
if globals.short_filenames:
m2 = full_manifest_re_short.search(filename)
else: m2 = full_manifest_re.search(filename)
if m1 or m2:
t = str2time((m1 or m2).group("time"))
if t:
if m1: return ParseResults("full", time = t,
volume_number = get_vol_num(m1.group("num")))
else: return ParseResults("full", time = t, manifest = 1)
return None
def check_inc():
"""Return ParseResults if file is from inc backup, None otherwise"""
if globals.short_filenames: m1 = inc_vol_re_short.search(filename)
else: m1 = inc_vol_re.search(filename)
if globals.short_filenames:
m2 = inc_manifest_re_short.search(filename)
else: m2 = inc_manifest_re.search(filename)
if m1 or m2:
t1 = str2time((m1 or m2).group("start_time"))
t2 = str2time((m1 or m2).group("end_time"))
if t1 and t2:
if m1: return ParseResults("inc", start_time = t1,
end_time = t2, volume_number = get_vol_num(m1.group("num")))
else: return ParseResults("inc", start_time = t1,
end_time = t2, manifest = 1)
return None
def check_sig():
"""Return ParseResults if file is a signature, None otherwise"""
if globals.short_filenames: m = full_sig_re_short.search(filename)
else: m = full_sig_re.search(filename)
if m:
t = str2time(m.group("time"))
if t: return ParseResults("full-sig", time = t)
else: return None
if globals.short_filenames: m = new_sig_re_short.search(filename)
else: m = new_sig_re.search(filename)
if m:
t1 = str2time(m.group("start_time"))
t2 = str2time(m.group("end_time"))
if t1 and t2:
return ParseResults("new-sig", start_time = t1, end_time = t2)
return None
def set_encryption_or_compression(pr):
"""Set encryption and compression flags in ParseResults pr"""
if (globals.short_filenames and filename.endswith('.z') or
not globals.short_filenames and filename.endswith('gz')):
pr.compressed = 1
else: pr.compressed = None
if (globals.short_filenames and filename.endswith('.g') or
not globals.short_filenames and filename.endswith('.gpg')):
pr.encrypted = 1
else: pr.encrypted = None
pr = check_full()
if not pr:
pr = check_inc()
if not pr:
pr = check_sig()
if not pr:
return None
set_encryption_or_compression(pr)
return pr
class ParseResults:
"""Hold information taken from a duplicity filename"""
def __init__(self, type, manifest = None, volume_number = None,
time = None, start_time = None, end_time = None,
encrypted = None, compressed = None):
assert (type == "full-sig" or type == "new-sig" or
type == "inc" or type == "full")
self.type = type
if type == "inc" or type == "full": assert manifest or volume_number
if type == "inc" or type == "new-sig": assert start_time and end_time
else: assert time
self.manifest = manifest
self.volume_number = volume_number
self.time = time
self.start_time, self.end_time = start_time, end_time
self.compressed = compressed # true if gzip compressed
self.encrypted = encrypted # true if gpg encrypted
syntax highlighted by Code2HTML, v. 0.9.1