1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Produce and parse the names of duplicity's backup files"""
23
24 import re
25 from duplicity import dup_time
26 from duplicity import globals
27
28 full_vol_re = re.compile("^duplicity-full"
29 "\\.(?P<time>.*?)"
30 "\\.vol(?P<num>[0-9]+)"
31 "\\.difftar"
32 "(?P<partial>(\\.part))?"
33 "($|\\.)")
34
35 full_vol_re_short = re.compile("^df"
36 "\\.(?P<time>[0-9a-z]+?)"
37 "\\.(?P<num>[0-9a-z]+)"
38 "\\.dt"
39 "(?P<partial>(\\.p))?"
40 "($|\\.)")
41
42 full_manifest_re = re.compile("^duplicity-full"
43 "\\.(?P<time>.*?)"
44 "\\.manifest"
45 "(?P<partial>(\\.part))?"
46 "($|\\.)")
47
48 full_manifest_re_short = re.compile("^df"
49 "\\.(?P<time>[0-9a-z]+?)"
50 "\\.m"
51 "(?P<partial>(\\.p))?"
52 "($|\\.)")
53
54 inc_vol_re = re.compile("^duplicity-inc"
55 "\\.(?P<start_time>.*?)"
56 "\\.to\\.(?P<end_time>.*?)"
57 "\\.vol(?P<num>[0-9]+)"
58 "\\.difftar"
59 "($|\\.)")
60
61 inc_vol_re_short = re.compile("^di"
62 "\\.(?P<start_time>[0-9a-z]+?)"
63 "\\.(?P<end_time>[0-9a-z]+?)"
64 "\\.(?P<num>[0-9a-z]+)"
65 "\\.dt"
66 "($|\\.)")
67
68 inc_manifest_re = re.compile("^duplicity-inc"
69 "\\.(?P<start_time>.*?)"
70 "\\.to"
71 "\\.(?P<end_time>.*?)"
72 "\\.manifest"
73 "(?P<partial>(\\.part))?"
74 "(\\.|$)")
75
76 inc_manifest_re_short = re.compile("^di"
77 "\\.(?P<start_time>[0-9a-z]+?)"
78 "\\.(?P<end_time>[0-9a-z]+?)"
79 "\\.m"
80 "(?P<partial>(\\.p))?"
81 "(\\.|$)")
82
83 full_sig_re = re.compile("^duplicity-full-signatures"
84 "\\.(?P<time>.*?)"
85 "\\.sigtar"
86 "(?P<partial>(\\.part))?"
87 "(\\.|$)")
88
89 full_sig_re_short = re.compile("^dfs"
90 "\\.(?P<time>[0-9a-z]+?)"
91 "\\.st"
92 "(?P<partial>(\\.p))?"
93 "(\\.|$)")
94
95 new_sig_re = re.compile("^duplicity-new-signatures"
96 "\\.(?P<start_time>.*?)"
97 "\\.to"
98 "\\.(?P<end_time>.*?)"
99 "\\.sigtar"
100 "(?P<partial>(\\.part))?"
101 "(\\.|$)")
102
103 new_sig_re_short = re.compile("^dns"
104 "\\.(?P<start_time>[0-9a-z]+?)"
105 "\\.(?P<end_time>[0-9a-z]+?)"
106 "\\.st"
107 "(?P<partial>(\\.p))?"
108 "(\\.|$)")
109
110
112 """
113 Return string representation of n in base 36 (use 0-9 and a-z)
114 """
115 div, mod = divmod(n, 36)
116 if mod <= 9:
117 last_digit = str(mod)
118 else:
119 last_digit = chr(ord('a') + mod - 10)
120 if n == mod:
121 return last_digit
122 else:
123 return to_base36(div)+last_digit
124
125
127 """
128 Convert string s in base 36 to long int
129 """
130 total = 0L
131 for i in range(len(s)):
132 total *= 36
133 digit_ord = ord(s[i])
134 if ord('0') <= digit_ord <= ord('9'):
135 total += digit_ord - ord('0')
136 elif ord('a') <= digit_ord <= ord('z'):
137 total += digit_ord - ord('a') + 10
138 else:
139 assert 0, "Digit %s in %s not in proper range" % (s[i], s)
140 return total
141
142
144 """
145 Return appropriate suffix depending on status of
146 encryption, compression, and short_filenames.
147 """
148 assert not (encrypted and gzipped)
149 if encrypted:
150 if globals.short_filenames:
151 suffix = '.g'
152 else:
153 suffix = ".gpg"
154 elif gzipped:
155 if globals.short_filenames:
156 suffix = ".z"
157 else:
158 suffix = '.gz'
159 else:
160 suffix = ""
161 return suffix
162
163
164 -def get(type, volume_number = None, manifest = False,
165 encrypted = False, gzipped = False, partial = False):
166 """
167 Return duplicity filename of specified type
168
169 type can be "full", "inc", "full-sig", or "new-sig". volume_number
170 can be given with the full and inc types. If manifest is true the
171 filename is of a full or inc manifest file.
172 """
173 assert dup_time.curtimestr
174 assert not (encrypted and gzipped)
175 suffix = get_suffix(encrypted, gzipped)
176 part_string = ""
177 if globals.short_filenames:
178 if partial:
179 part_string = ".p"
180 else:
181 if partial:
182 part_string = ".part"
183
184 if type == "full-sig" or type == "new-sig":
185 assert not volume_number and not manifest
186 assert not (volume_number and part_string)
187 if type == "full-sig":
188 if globals.short_filenames:
189 return ("dfs.%s.st%s%s" %
190 (to_base36(dup_time.curtime), part_string, suffix))
191 else:
192 return ("duplicity-full-signatures.%s.sigtar%s%s" %
193 (dup_time.curtimestr, part_string, suffix))
194 elif type == "new-sig":
195 if globals.short_filenames:
196 return ("dns.%s.%s.st%s%s" %
197 (to_base36(dup_time.prevtime), to_base36(dup_time.curtime),
198 part_string, suffix))
199 else:
200 return ("duplicity-new-signatures.%s.to.%s.sigtar%s%s" %
201 (dup_time.prevtimestr, dup_time.curtimestr,
202 part_string, suffix))
203 else:
204 assert volume_number or manifest
205 assert not (volume_number and manifest)
206 if volume_number:
207 if globals.short_filenames:
208 vol_string = "%s.dt" % to_base36(volume_number)
209 else:
210 vol_string = "vol%d.difftar" % volume_number
211 else:
212 if globals.short_filenames:
213 vol_string = "m"
214 else:
215 vol_string = "manifest"
216 if type == "full":
217 if globals.short_filenames:
218 return ("df.%s.%s%s%s" % (to_base36(dup_time.curtime),
219 vol_string, part_string, suffix))
220 else:
221 return ("duplicity-full.%s.%s%s%s" % (dup_time.curtimestr,
222 vol_string, part_string, suffix))
223 elif type == "inc":
224 if globals.short_filenames:
225 return ("di.%s.%s.%s%s%s" % (to_base36(dup_time.prevtime),
226 to_base36(dup_time.curtime),
227 vol_string, part_string, suffix))
228 else:
229 return ("duplicity-inc.%s.to.%s.%s%s%s" % (dup_time.prevtimestr,
230 dup_time.curtimestr,
231 vol_string, part_string, suffix))
232 else:
233 assert 0
234
235
237 """
238 Parse duplicity filename, return None or ParseResults object
239 """
240 filename = filename.lower()
241 def str2time(timestr, short):
242 """
243 Return time in seconds if string can be converted, None otherwise
244 """
245 if short:
246 t = from_base36(timestr)
247 else:
248 try:
249 t = dup_time.genstrtotime(timestr.upper())
250 except dup_time.TimeException:
251 return None
252 return t
253
254 def get_vol_num(s, short):
255 """
256 Return volume number from volume number string
257 """
258 if short:
259 return from_base36(s)
260 else:
261 return int(s)
262
263 def check_full():
264 """
265 Return ParseResults if file is from full backup, None otherwise
266 """
267 short = True
268 m1 = full_vol_re_short.search(filename)
269 m2 = full_manifest_re_short.search(filename)
270 if not m1 and not m2 and not globals.short_filenames:
271 short = False
272 m1 = full_vol_re.search(filename)
273 m2 = full_manifest_re.search(filename)
274 if m1 or m2:
275 t = str2time((m1 or m2).group("time"), short)
276 if t:
277 if m1:
278 return ParseResults("full", time = t,
279 volume_number = get_vol_num(m1.group("num"), short))
280 else:
281 return ParseResults("full", time = t, manifest = True,
282 partial = (m2.group("partial") != None))
283 return None
284
285 def check_inc():
286 """
287 Return ParseResults if file is from inc backup, None otherwise
288 """
289 short = True
290 m1 = inc_vol_re_short.search(filename)
291 m2 = inc_manifest_re_short.search(filename)
292 if not m1 and not m2 and not globals.short_filenames:
293 short = False
294 m1 = inc_vol_re.search(filename)
295 m2 = inc_manifest_re.search(filename)
296 if m1 or m2:
297 t1 = str2time((m1 or m2).group("start_time"), short)
298 t2 = str2time((m1 or m2).group("end_time"), short)
299 if t1 and t2:
300 if m1:
301 return ParseResults("inc", start_time = t1,
302 end_time = t2, volume_number = get_vol_num(m1.group("num"), short))
303 else:
304 return ParseResults("inc", start_time = t1, end_time = t2, manifest = 1,
305 partial = (m2.group("partial") != None))
306 return None
307
308 def check_sig():
309 """
310 Return ParseResults if file is a signature, None otherwise
311 """
312 short = True
313 m = full_sig_re_short.search(filename)
314 if not m and not globals.short_filenames:
315 short = False
316 m = full_sig_re.search(filename)
317 if m:
318 t = str2time(m.group("time"), short)
319 if t:
320 return ParseResults("full-sig", time = t,
321 partial = (m.group("partial") != None))
322 else:
323 return None
324
325 short = True
326 m = new_sig_re_short.search(filename)
327 if not m and not globals.short_filenames:
328 short = False
329 m = new_sig_re.search(filename)
330 if m:
331 t1 = str2time(m.group("start_time"), short)
332 t2 = str2time(m.group("end_time"), short)
333 if t1 and t2:
334 return ParseResults("new-sig", start_time = t1, end_time = t2,
335 partial = (m.group("partial") != None))
336 return None
337
338 def set_encryption_or_compression(pr):
339 """
340 Set encryption and compression flags in ParseResults pr
341 """
342 if (filename.endswith('.z') or
343 not globals.short_filenames and filename.endswith('gz')):
344 pr.compressed = 1
345 else:
346 pr.compressed = None
347
348 if (filename.endswith('.g') or
349 not globals.short_filenames and filename.endswith('.gpg')):
350 pr.encrypted = 1
351 else:
352 pr.encrypted = None
353
354 pr = check_full()
355 if not pr:
356 pr = check_inc()
357 if not pr:
358 pr = check_sig()
359 if not pr:
360 return None
361 set_encryption_or_compression(pr)
362 return pr
363
364
366 """
367 Hold information taken from a duplicity filename
368 """
369 - def __init__(self, type, manifest = None, volume_number = None,
370 time = None, start_time = None, end_time = None,
371 encrypted = None, compressed = None, partial = False):
372
373 assert type in ["full-sig", "new-sig", "inc", "full"]
374
375 self.type = type
376 if type == "inc" or type == "full":
377 assert manifest or volume_number
378 if type == "inc" or type == "new-sig":
379 assert start_time and end_time
380 else:
381 assert time
382
383 self.manifest = manifest
384 self.volume_number = volume_number
385 self.time = time
386 self.start_time, self.end_time = start_time, end_time
387
388 self.compressed = compressed
389 self.encrypted = encrypted
390
391 self.partial = partial
392