Source code for duplicity.file_naming

# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2002 Ben Escoto <ben@emerose.org>
# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
#
# 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 2 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
from duplicity import dup_time
from duplicity import globals

full_vol_re = None
full_vol_re_short = None
full_manifest_re = None
full_manifest_re_short = None
inc_vol_re = None
inc_vol_re_short = None
inc_manifest_re = None
inc_manifest_re_short = None
full_sig_re = None
full_sig_re_short = None
new_sig_re = None
new_sig_re_short = None


[docs]def prepare_regex(force=False): global full_vol_re global full_vol_re_short global full_manifest_re global full_manifest_re_short global inc_vol_re global inc_vol_re_short global inc_manifest_re global inc_manifest_re_short global full_sig_re global full_sig_re_short global new_sig_re global new_sig_re_short # we force regex re-generation in unit tests because file prefixes might have changed if full_vol_re and not force: return full_vol_re = re.compile("^" + globals.file_prefix + globals.file_prefix_archive + "duplicity-full" "\\.(?P<time>.*?)" "\\.vol(?P<num>[0-9]+)" "\\.difftar" "(?P<partial>(\\.part))?" "($|\\.)") full_vol_re_short = re.compile("^" + globals.file_prefix + globals.file_prefix_archive + "df" "\\.(?P<time>[0-9a-z]+?)" "\\.(?P<num>[0-9a-z]+)" "\\.dt" "(?P<partial>(\\.p))?" "($|\\.)") full_manifest_re = re.compile("^" + globals.file_prefix + globals.file_prefix_manifest + "duplicity-full" "\\.(?P<time>.*?)" "\\.manifest" "(?P<partial>(\\.part))?" "($|\\.)") full_manifest_re_short = re.compile("^" + globals.file_prefix + globals.file_prefix_manifest + "df" "\\.(?P<time>[0-9a-z]+?)" "\\.m" "(?P<partial>(\\.p))?" "($|\\.)") inc_vol_re = re.compile("^" + globals.file_prefix + globals.file_prefix_archive + "duplicity-inc" "\\.(?P<start_time>.*?)" "\\.to\\.(?P<end_time>.*?)" "\\.vol(?P<num>[0-9]+)" "\\.difftar" "($|\\.)") inc_vol_re_short = re.compile("^" + globals.file_prefix + globals.file_prefix_archive + "di" "\\.(?P<start_time>[0-9a-z]+?)" "\\.(?P<end_time>[0-9a-z]+?)" "\\.(?P<num>[0-9a-z]+)" "\\.dt" "($|\\.)") inc_manifest_re = re.compile("^" + globals.file_prefix + globals.file_prefix_manifest + "duplicity-inc" "\\.(?P<start_time>.*?)" "\\.to" "\\.(?P<end_time>.*?)" "\\.manifest" "(?P<partial>(\\.part))?" "(\\.|$)") inc_manifest_re_short = re.compile("^" + globals.file_prefix + globals.file_prefix_manifest + "di" "\\.(?P<start_time>[0-9a-z]+?)" "\\.(?P<end_time>[0-9a-z]+?)" "\\.m" "(?P<partial>(\\.p))?" "(\\.|$)") full_sig_re = re.compile("^" + globals.file_prefix + globals.file_prefix_signature + "duplicity-full-signatures" "\\.(?P<time>.*?)" "\\.sigtar" "(?P<partial>(\\.part))?" "(\\.|$)") full_sig_re_short = re.compile("^" + globals.file_prefix + globals.file_prefix_signature + "dfs" "\\.(?P<time>[0-9a-z]+?)" "\\.st" "(?P<partial>(\\.p))?" "(\\.|$)") new_sig_re = re.compile("^" + globals.file_prefix + globals.file_prefix_signature + "duplicity-new-signatures" "\\.(?P<start_time>.*?)" "\\.to" "\\.(?P<end_time>.*?)" "\\.sigtar" "(?P<partial>(\\.part))?" "(\\.|$)") new_sig_re_short = re.compile("^" + globals.file_prefix + globals.file_prefix_signature + "dns" "\\.(?P<start_time>[0-9a-z]+?)" "\\.(?P<end_time>[0-9a-z]+?)" "\\.st" "(?P<partial>(\\.p))?" "(\\.|$)")
[docs]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
[docs]def from_base36(s): """ Convert string s in base 36 to long int """ total = 0 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
[docs]def get_suffix(encrypted, gzipped): """ Return appropriate suffix depending on status of encryption, compression, and short_filenames. """ if encrypted: gzipped = False if encrypted: if globals.short_filenames: suffix = '.g' else: suffix = ".gpg" elif gzipped: if globals.short_filenames: suffix = ".z" else: suffix = '.gz' else: suffix = "" return suffix
[docs]def get(type, volume_number=None, manifest=False, encrypted=False, gzipped=False, partial=False): """ 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 if encrypted: gzipped = False suffix = get_suffix(encrypted, gzipped) part_string = "" if globals.short_filenames: if partial: part_string = ".p" else: if partial: part_string = ".part" if type == "full-sig" or type == "new-sig": assert not volume_number and not manifest assert not (volume_number and part_string) if type == "full-sig": if globals.short_filenames: return (globals.file_prefix + globals.file_prefix_signature + "dfs.%s.st%s%s" % (to_base36(dup_time.curtime), part_string, suffix)) else: return (globals.file_prefix + globals.file_prefix_signature + "duplicity-full-signatures.%s.sigtar%s%s" % (dup_time.curtimestr, part_string, suffix)) elif type == "new-sig": if globals.short_filenames: return (globals.file_prefix + globals.file_prefix_signature + "dns.%s.%s.st%s%s" % (to_base36(dup_time.prevtime), to_base36(dup_time.curtime), part_string, suffix)) else: return (globals.file_prefix + globals.file_prefix_signature + "duplicity-new-signatures.%s.to.%s.sigtar%s%s" % (dup_time.prevtimestr, dup_time.curtimestr, part_string, suffix)) else: assert volume_number or manifest assert not (volume_number and manifest) prefix = globals.file_prefix if volume_number: if globals.short_filenames: vol_string = "%s.dt" % to_base36(volume_number) else: vol_string = "vol%d.difftar" % volume_number prefix += globals.file_prefix_archive else: if globals.short_filenames: vol_string = "m" else: vol_string = "manifest" prefix += globals.file_prefix_manifest if type == "full": if globals.short_filenames: return ("%sdf.%s.%s%s%s" % (prefix, to_base36(dup_time.curtime), vol_string, part_string, suffix)) else: return ("%sduplicity-full.%s.%s%s%s" % (prefix, dup_time.curtimestr, vol_string, part_string, suffix)) elif type == "inc": if globals.short_filenames: return ("%sdi.%s.%s.%s%s%s" % (prefix, to_base36(dup_time.prevtime), to_base36(dup_time.curtime), vol_string, part_string, suffix)) else: return ("%sduplicity-inc.%s.to.%s.%s%s%s" % (prefix, dup_time.prevtimestr, dup_time.curtimestr, vol_string, part_string, suffix)) else: assert 0
[docs]def parse(filename): """ Parse duplicity filename, return None or ParseResults object """ def str2time(timestr, short): """ Return time in seconds if string can be converted, None otherwise """ if short: 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, short): """ Return volume number from volume number string """ if short: return from_base36(s) else: return int(s) def check_full(): """ Return ParseResults if file is from full backup, None otherwise """ prepare_regex() short = True m1 = full_vol_re_short.search(filename) m2 = full_manifest_re_short.search(filename) if not m1 and not m2 and not globals.short_filenames: short = False m1 = full_vol_re.search(filename) m2 = full_manifest_re.search(filename) if m1 or m2: t = str2time((m1 or m2).group("time"), short) if t: if m1: return ParseResults("full", time=t, volume_number=get_vol_num(m1.group("num"), short)) else: return ParseResults("full", time=t, manifest=True, partial=(m2.group("partial") is not None)) return None def check_inc(): """ Return ParseResults if file is from inc backup, None otherwise """ prepare_regex() short = True m1 = inc_vol_re_short.search(filename) m2 = inc_manifest_re_short.search(filename) if not m1 and not m2 and not globals.short_filenames: short = False m1 = inc_vol_re.search(filename) m2 = inc_manifest_re.search(filename) if m1 or m2: t1 = str2time((m1 or m2).group("start_time"), short) t2 = str2time((m1 or m2).group("end_time"), short) if t1 and t2: if m1: return ParseResults("inc", start_time=t1, end_time=t2, volume_number=get_vol_num(m1.group("num"), short)) else: return ParseResults("inc", start_time=t1, end_time=t2, manifest=1, partial=(m2.group("partial") is not None)) return None def check_sig(): """ Return ParseResults if file is a signature, None otherwise """ prepare_regex() short = True m = full_sig_re_short.search(filename) if not m and not globals.short_filenames: short = False m = full_sig_re.search(filename) if m: t = str2time(m.group("time"), short) if t: return ParseResults("full-sig", time=t, partial=(m.group("partial") is not None)) else: return None short = True m = new_sig_re_short.search(filename) if not m and not globals.short_filenames: short = False m = new_sig_re.search(filename) if m: t1 = str2time(m.group("start_time"), short) t2 = str2time(m.group("end_time"), short) if t1 and t2: return ParseResults("new-sig", start_time=t1, end_time=t2, partial=(m.group("partial") is not None)) return None def set_encryption_or_compression(pr): """ Set encryption and compression flags in ParseResults pr """ if (filename.endswith('.z') or not globals.short_filenames and filename.endswith('gz')): pr.compressed = 1 else: pr.compressed = None if (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
[docs]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, partial=False): assert type in ["full-sig", "new-sig", "inc", "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 self.partial = partial