# -*- coding: utf-8 -*-
""" 基於檔案的時戳衍生 / Time-stamp from file attributes """
# 注意: 這個模組參考到了其他 commonutil 內的模組。
# 當製作 commonutil 模組時如果引用此模組,必須小心不要製造出遞迴的參考。
# Caution: as this module includes other modules from commonutil package.
# be careful, do not make circular reference if building commonutil module base on this module.
import os
import math
import time
import datetime
import logging
from commonutil.convert import to_bool
from commonutil.convert import to_integer
from commonutil.ticktock import get_tz_offset_info
from commonutil.pathtag import parse_template as parse_ptag_template
from commonutil.pathtag import replace as replace_ptag
from commonutil.fetch import get_by_keys
_log = logging.getLogger(__name__)
_DEFAULT_TIMETAG = (
1970,
1,
1,
0,
0,
0,
)
[docs]class NoopBuilder(object):
""" 總是傳回預設的 TimeTag / Always result in default time-tag """
[docs] def build(self, current_filepath, filename_regxmobj, pathname_regxmobj, *args, **kwds): # pylint: disable=unused-argument
# type: (str, re.MatchObject, re.MatchObject) -> datetime.datetime
"""
依據設定值取出 TimeTag / Build time tag according to configuration
Args:
current_filepath - 目前檔案路徑
filename_regxmobj: 比對狀況物件,檔案部份 (re.MatchObject) / Match object of file name
pathname_regxmobj: 比對狀況物件,路徑部份 (re.MatchObject) / Match object of path (folder) name
Return:
表示 TimeTag 的 datetime.datetime 物件 / Result datetime.datetime object representing time-tag
"""
# TODO: 註: 儘可能在這個地方保持最完整的 method prototype
return datetime.datetime(*_DEFAULT_TIMETAG)
def _ptag_substitude(rulename, ruleobj, parm_ptag_replace, default_value=None):
""" (internal) 進行 PathTag Match Rule 的轉換 / Perform PathTag match rule substitude
Args:
rulename: 規則名稱 (顯示在轉換失敗的 log 中) / Rule name (use in log of failed substitude)
ruleobj: 規則物件 / Rule object
parm_ptag_replace: 用做呼叫 pathtag.replace() 函數的參數集 / Parameter tuple for invoking pathtag.replace()
default_value=None: 預設值 / Default value
Return:
轉換後的數值 / Translated value
"""
if ruleobj is None:
return default_value
v = replace_ptag(ruleobj, *parm_ptag_replace)
if v is not None:
try:
return int(v)
except Exception as e:
_log.exception("cannot convert match rule with %r: %r (%r)", rulename, v, e)
return default_value
[docs]class PathTagBuilder(NoopBuilder):
""" 基於 Path Tag 產生 TimeTag / Build time-tag based on path-tag """
def __init__(self,
year_4d_rule=None,
year_2d_rule=None,
month_rule=None,
day_rule=None,
hour_rule=None,
minute_rule=None,
second_rule=None,
baseoffset_sec=None,
labeloffset_sec=None,
period_sec=None,
timezone_feedback=False,
*args,
**kwds): # pylint: disable=too-many-arguments
# type: (str, str, str, str, str, str, str, Any, Any, Any, Any) -> None
"""
Args:
year_4d_rule=None: 四位數年份的來源規則,與 year_2d_rule 擇一給定 / Rule of 4-digit year (only one of year_4d_rule or year_2d_rule shall be given)
year_2d_rule=None: 二位數年份的來源規則,前面會冠上 20 作為前兩位數,也就是產生的年份為 20xx 年,與 year_4d_rule 擇一給定 / Rule of 2-digit year (only one of year_{4d, 2d}_rule shall given)
month_rule=None: 月份的來源規則,未給定時使用預設值 1 / Rule of month. Default to 1 if not given.
day_rule=None: 日的來源規則,未給定時使用預設值 1 / Rule of day. Default to 1 if not given.
hour_rule=None: 時的來源規則,未給定時使用預設值 0 / Rule of hour. Default to 0 if not given.
minute_rule=None: 分的來源規則,未給定時使用預設值 0 / Rule of minute. Default to 0 if not given.
second_rule=None: 秒的來源規則,未給定時使用預設值 0 / Rule of second. Default to 0 if not given.
baseoffset_sec=None: 產生的時間標記的起點秒數,預設為 0 / Base offset of time-tag sequence. Default to 0 if not given.
labeloffset_sec=None: 產生的時間標記的標記偏移秒數,預設為 0 / Label (Time-tag) offset to time frame. Default to 0 if not given.
period_sec=None: 時間週期的長度 / Period in seconds
timezone_feedback=False: 是否在計算時進行時區的回饋 / Time-zone feedback on computation
"""
super(PathTagBuilder, self).__init__(*args, **kwds)
# {{{ time rules
self.year_4d_rule = parse_ptag_template(year_4d_rule)
self.year_2d_rule = parse_ptag_template(year_2d_rule)
self.month_rule = parse_ptag_template(month_rule)
self.day_rule = parse_ptag_template(day_rule)
self.hour_rule = parse_ptag_template(hour_rule)
self.minute_rule = parse_ptag_template(minute_rule)
self.second_rule = parse_ptag_template(second_rule)
# }}} time rules
self.mtime_baseoffset_sec = to_integer(baseoffset_sec, 0)
self.mtime_labeloffset_sec = to_integer(labeloffset_sec, 0)
self.mtime_period_sec = to_integer(period_sec)
self.mtime_timezone_feedback = to_bool(timezone_feedback, False)
[docs] def build(self, current_filepath, filename_regxmobj, pathname_regxmobj, *args, **kwds): # pylint: disable=too-many-locals
"""
依據設定值取出 TimeTag / Build time tag according to configuration
Args:
current_filepath - 目前檔案路徑
filename_regxmobj: 比對狀況物件,檔案部份 (re.MatchObject) / Match object of file name
pathname_regxmobj: 比對狀況物件,路徑部份 (re.MatchObject) / Match object of path (folder) name
Return:
表示 TimeTag 的 datetime.datetime 物件 / Result datetime.datetime object representing time-tag
"""
year, month, day, hour, minute, second, = _DEFAULT_TIMETAG
if self.mtime_period_sec is None:
file_mtime = datetime.datetime.fromtimestamp(os.path.getmtime(current_filepath) - self.mtime_baseoffset_sec + self.mtime_labeloffset_sec)
else:
if self.mtime_timezone_feedback:
utc_offset_feedback, _tzname, = get_tz_offset_info()
else:
utc_offset_feedback = 0
aux = (math.floor((os.path.getmtime(current_filepath) - utc_offset_feedback - self.mtime_baseoffset_sec) / self.mtime_period_sec) *
self.mtime_period_sec) + self.mtime_labeloffset_sec + utc_offset_feedback
file_mtime = datetime.datetime.fromtimestamp(aux)
parm_ptag_replace = (
filename_regxmobj,
pathname_regxmobj,
file_mtime,
)
# {{{ run replacement
if self.year_4d_rule is not None:
year = _ptag_substitude("year-4d", self.year_4d_rule, parm_ptag_replace, year)
elif self.year_2d_rule is not None:
year = 2000 + _ptag_substitude("year-2d", self.year_2d_rule, parm_ptag_replace, year - 2000)
month = _ptag_substitude("month", self.month_rule, parm_ptag_replace, month)
day = _ptag_substitude("day", self.day_rule, parm_ptag_replace, day)
hour = _ptag_substitude("hour", self.hour_rule, parm_ptag_replace, hour)
minute = _ptag_substitude("minute", self.minute_rule, parm_ptag_replace, minute)
second = _ptag_substitude("second", self.second_rule, parm_ptag_replace, second)
# }}} run replacement
return datetime.datetime(year, month, day, hour, minute, second)
[docs]class FileTimeBuilder(NoopBuilder):
""" 利用檔案修改時間產生 TimeTag / Build time-tag with file modification time """
def __init__(self, filetime_rule, min_period, base_offset, label_offset, *args, **kwds):
# type: (str, int, int)
"""
Args:
filetime_rule: 使用到的時間元素 (字母 YMDhms 分別表示年月日時分秒) / Time element to apply (YMDhms represents year, month, day, hour, minute and second respectively)
min_period: 最小時間週期 / Minimum period of time-tag series
base_offset: 時序起點的偏移秒數 / Offset seconds to beginning of time-tag series
label_offset: 標記的偏移秒數 / Offset seconds from beginning of time frame to time-tag
"""
super(FileTimeBuilder, self).__init__(*args, **kwds)
self.filetime_rule = str(filetime_rule)
self.min_period = to_integer(min_period, None)
self.base_offset = to_integer(base_offset, 0)
self.label_offset = to_integer(label_offset, 0)
if (self.min_period is not None) and (self.min_period < 1):
self.min_period = None
[docs] def build(self, current_filepath, *args, **kwds): # pylint: disable=arguments-differ
"""
依據設定值取出 TimeTag / Build time tag according to configuration
Args:
current_filepath - 目前檔案路徑
Return:
表示 TimeTag 的 datetime.datetime 物件 / Result datetime.datetime object representing time-tag
"""
year, month, day, hour, minute, second, = _DEFAULT_TIMETAG
# {{{ compute time-tag
time_start = time.localtime(0)
filetstamp = os.path.getmtime(current_filepath)
if self.min_period is not None:
if time_start[3] == 8:
filetstamp = (math.floor((filetstamp + 28800 - self.base_offset) / self.min_period) * self.min_period) + self.label_offset - 28800
else:
filetstamp = (math.floor((filetstamp - self.base_offset) / self.min_period) * self.min_period) + self.label_offset
filetime = datetime.datetime.fromtimestamp(filetstamp)
# }}} compute time-tag
_log.debug("filetime: %r", filetime)
r = self.filetime_rule
if 'Y' in r:
year = filetime.year
if 'M' in r:
month = filetime.month
if 'D' in r:
day = filetime.day
if 'h' in r:
hour = filetime.hour
if 'm' in r:
minute = filetime.minute
if 's' in r:
second = filetime.second
return datetime.datetime(year, month, day, hour, minute, second)
[docs]def parse_build_rule(cmap):
# type: (Dict[str, Any]) -> NoopBuilder
"""
解析建立 Time-tag 的規則
Parse time-tag build rule from dictionary
Args:
cmap: dict object contains build rule configuration
Return:
從檔案屬性建立 Time-tag 的 Builder 物件 / Object to build time-tag from file attributes
"""
if ('year_4d' in cmap) or ('year_2d' in cmap):
return PathTagBuilder(
cmap.get('year_4d'), cmap.get('year_2d'), cmap.get('month'), cmap.get('day'), cmap.get('hour'), cmap.get('minute'), cmap.get('second'),
cmap.get('mtime-base-offset'), cmap.get('mtime-label-offset'), get_by_keys(cmap, (
'mtime-period',
'mtime-freq',
)), cmap.get('mtime-timezone-feedback'))
elif 'use-file-time' in cmap:
return FileTimeBuilder(cmap.get('use-file-time'), get_by_keys(cmap, (
'period',
'frequency',
)), cmap.get('base-offset'), cmap.get('label-offset'))
return NoopBuilder()
# vim: ts=4 sw=4 ai nowrap