Skip to content

Commit

Permalink
Allow unixtime-style timestamp extraction
Browse files Browse the repository at this point in the history
Some backup solutions prefer using UNIX timestamps for tagging backup
files or directories. In order to enable the rotation of such
files/directories some extra handling is added to enable scanning for UNIX
timestamps in file- or directory-names.
  • Loading branch information
Roland Sommer committed Feb 8, 2021
1 parent 32e966c commit 1060e30
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 18 deletions.
5 changes: 3 additions & 2 deletions README.rst
Expand Up @@ -155,8 +155,9 @@ intended you have no right to complain ;-).
usage of the ``-H``, ``--hourly`` option for details about ``COUNT``."
"``-t``, ``--timestamp-pattern=PATTERN``","Customize the regular expression pattern that is used to match and extract
timestamps from filenames. ``PATTERN`` is expected to be a Python compatible
regular expression that must define the named capture groups 'year',
'month' and 'day' and may define 'hour', 'minute' and 'second'."
regular expression that must define a named capture group 'unixtime' or the
named capture groups 'year', 'month' and 'day' and may define 'hour',
'minute' and 'second'."
"``-I``, ``--include=PATTERN``","Only process backups that match the shell pattern given by ``PATTERN``. This
argument can be repeated. Make sure to quote ``PATTERN`` so the shell doesn't
expand the pattern before it's received by rotate-backups."
Expand Down
59 changes: 45 additions & 14 deletions rotate_backups/__init__.py
Expand Up @@ -447,6 +447,16 @@ def strict(self):
"""
return True

@mutable_property
def _is_unixtime(self):
"""
Is the given pattern used to extract a unix timestamp?
This private property reflects if the given regex is used to exctract
a unix timestamp from file- or directorynames.
"""
return False

@mutable_property
def timestamp_pattern(self):
"""
Expand All @@ -458,8 +468,9 @@ def timestamp_pattern(self):
:func:`re.compile()` documentation for details).
The regular expression pattern is expected to be a Python compatible
regular expression that defines the named capture groups 'year',
'month' and 'day' and optionally 'hour', 'minute' and 'second'.
regular expression that defines the named capture group 'unixtime' or
the named capture groups 'year', 'month' and 'day' and optionally
'hour', 'minute' and 'second'.
String values are automatically coerced to compiled regular expressions
by calling :func:`~humanfriendly.coerce_pattern()`, in this case only
Expand All @@ -476,10 +487,15 @@ def timestamp_pattern(self):
def timestamp_pattern(self, value):
"""Coerce the value of :attr:`timestamp_pattern` to a compiled regular expression."""
pattern = coerce_pattern(value, re.VERBOSE)
for component, required in SUPPORTED_DATE_COMPONENTS:
if component not in pattern.groupindex and required:
raise ValueError("Pattern is missing required capture group! (%s)" % component)
set_property(self, 'timestamp_pattern', pattern)
if "unixtime" in pattern.groupindex:
set_property(self, 'timestamp_pattern', pattern)
self._is_unixtime = True
else:
for component, required in SUPPORTED_DATE_COMPONENTS:
if component not in pattern.groupindex and required:
raise ValueError("Pattern is missing required capture group! (%s)" % component)
set_property(self, 'timestamp_pattern', pattern)
self._is_unixtime = False

def rotate_concurrent(self, *locations, **kw):
"""
Expand Down Expand Up @@ -678,15 +694,30 @@ def match_to_datetime(self, match):
"""
kw = {}
captures = match.groupdict()
for component, required in SUPPORTED_DATE_COMPONENTS:
value = captures.get(component)
if value:
kw[component] = int(value, 10)
elif required:
raise ValueError("Missing required date component! (%s)" % component)
if self._is_unixtime:
base = int(match.groupdict().get("unixtime"))
# Try seconds- and milliseconds-precision timestamps.
for value in (base, base / 1000):
try:
timestamp = datetime.datetime.fromtimestamp(value)
break
except ValueError:
timestamp = None
if timestamp is None:
logger.notice("Ignoring %s due to invalid date (%s).", value, match.group())
else:
kw[component] = 0
return datetime.datetime(**kw)
logger.verbose("Extracted timestamp %r from %r", timestamp, value)
return timestamp
else:
for component, required in SUPPORTED_DATE_COMPONENTS:
value = captures.get(component)
if value:
kw[component] = int(value, 10)
elif required:
raise ValueError("Missing required date component! (%s)" % component)
else:
kw[component] = 0
return datetime.datetime(**kw)

def group_backups(self, backups):
"""
Expand Down
5 changes: 3 additions & 2 deletions rotate_backups/cli.py
Expand Up @@ -73,8 +73,9 @@
Customize the regular expression pattern that is used to match and extract
timestamps from filenames. PATTERN is expected to be a Python compatible
regular expression that must define the named capture groups 'year',
'month' and 'day' and may define 'hour', 'minute' and 'second'.
regular expression that must define the named capture group 'unixtime' or
the named capture groups 'year', 'month' and 'day' and may define 'hour',
'minute' and 'second'.
-I, --include=PATTERN
Expand Down

0 comments on commit 1060e30

Please sign in to comment.