__init__.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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, archive_dir_path, primary_chain):
  30. self.archive_dir_path = archive_dir_path
  31. self.primary_chain = primary_chain
  32. @property
  33. def last_incremental_backup_time(self):
  34. return self.primary_chain.last_backup_time if self.primary_chain else None
  35. @classmethod
  36. def _parse(cls, text):
  37. if 'No backup chains with active signatures found' in text:
  38. primary_chain = None
  39. else:
  40. primary_chain_match = re.search(
  41. '^Found primary backup chain.*\s{sep}([\w\W]*?)\s{sep}'.format(
  42. sep=_CollectionStatus.chain_separator_regex,
  43. ),
  44. text,
  45. re.MULTILINE,
  46. )
  47. primary_chain = _ChainStatus._parse(
  48. text=primary_chain_match.group(1),
  49. )
  50. return cls(
  51. archive_dir_path=re.search(r'Archive dir: (.*)', text).group(1),
  52. primary_chain=primary_chain,
  53. )
  54. class _ChainStatus(_Status):
  55. def __init__(self, sets):
  56. self.sets = sets
  57. @property
  58. def last_backup_time(self):
  59. return max([s.backup_time for s in self.sets])
  60. @classmethod
  61. def _parse(cls, text):
  62. sets = []
  63. set_lines = re.split(r'Num volumes: *\r?\n', text)[1]
  64. for set_line in re.split(r'\r?\n', set_lines):
  65. set_attr = re.match(
  66. r'\s*(?P<mode>\w+) {2,}(?P<ts>.+?) {2,} (?P<vol>\d+)',
  67. set_line,
  68. ).groupdict()
  69. # duplicity uses time.asctime().
  70. # time.strptime() without format inverts time.asctime().
  71. sets.append(_SetStatus(
  72. backup_time=_parse_duplicity_timestamp(set_attr['ts']),
  73. ))
  74. return cls(sets=sets)
  75. class _SetStatus(_Status):
  76. def __init__(self, backup_time):
  77. self.backup_time = backup_time