__init__.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import datetime
  2. import re
  3. import subprocess
  4. import sys
  5. import time
  6. def _duplicity(params):
  7. stdout = subprocess.check_output(
  8. ['duplicity'] + params,
  9. )
  10. return stdout.decode(sys.stdout.encoding)
  11. def _parse_duplicity_timestamp(timestamp):
  12. return datetime.datetime.fromtimestamp(
  13. time.mktime(time.strptime(timestamp))
  14. )
  15. class Collection(object):
  16. def __init__(self, url):
  17. self.url = url
  18. def request_status(self):
  19. return _CollectionStatus._parse(
  20. text=_duplicity(['collection-status', self.url])
  21. )
  22. class _Status(object):
  23. def __eq__(self, other):
  24. return isinstance(self, type(other)) and vars(self) == vars(other)
  25. def __neq__(self, other):
  26. return not (self == other)
  27. class _CollectionStatus(_Status):
  28. chain_separator_regex = r'-{25}\s'
  29. def __init__(self, primary_chain):
  30. self.primary_chain = primary_chain
  31. @classmethod
  32. def _parse(cls, text):
  33. if 'No backup chains with active signatures found' in text:
  34. primary_chain = None
  35. else:
  36. primary_chain_match = re.search(
  37. '^Found primary backup chain.*\s{sep}([\w\W]*?)\s{sep}'.format(
  38. sep=_CollectionStatus.chain_separator_regex,
  39. ),
  40. text,
  41. re.MULTILINE,
  42. )
  43. primary_chain = _ChainStatus._parse(
  44. text=primary_chain_match.group(1),
  45. )
  46. return cls(
  47. primary_chain=primary_chain,
  48. )
  49. class _ChainStatus(_Status):
  50. def __init__(self, sets):
  51. self.sets = sets
  52. @property
  53. def last_backup_time(self):
  54. return max([s.backup_time for s in self.sets])
  55. @classmethod
  56. def _parse(cls, text):
  57. sets = []
  58. set_lines = re.split(r'Num volumes: *\r?\n', text)[1]
  59. for set_line in re.split(r'\r?\n', set_lines):
  60. set_attr = re.match(
  61. r'\s*(?P<mode>\w+) {2,}(?P<ts>.+?) {2,} (?P<vol>\d+)',
  62. set_line,
  63. ).groupdict()
  64. # duplicity uses time.asctime().
  65. # time.strptime() without format inverts time.asctime().
  66. sets.append(_SetStatus(
  67. backup_time=_parse_duplicity_timestamp(set_attr['ts']),
  68. ))
  69. return cls(sets=sets)
  70. class _SetStatus(_Status):
  71. def __init__(self, backup_time):
  72. self.backup_time = backup_time