-
Notifications
You must be signed in to change notification settings - Fork 17
/
republisher.py
125 lines (88 loc) · 3.29 KB
/
republisher.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import datetime
import logging
import pathlib
import pickle
import re
import typing
import time
import appdirs
from publish import exceptions, config as config_module, APP_NAME
logger = logging.getLogger('publish.republisher')
SLEEP_BETWEEN_CHECKS = 60 # seconds
ACCEPTED_DELTA_FOR_PUBLISHING = datetime.timedelta(minutes=10)
LIFETIME_SYNTAX_REGEX = r'(?:(\d+)(h|m|s)(?!.*\2)\s?)'
LIFETIME_SYNTAX_CHECK_REGEX = f'^{LIFETIME_SYNTAX_REGEX}+?$'
LIFETIME_MAPPING = {
'h': 'hours',
'm': 'minutes',
's': 'seconds',
}
class PublishingDatabase:
def __init__(self, data: dict, path: typing.Optional[pathlib.Path] = None):
self.data = data
self.path = path
self.data_changed = False
def __getitem__(self, item):
return self.data[item]
def __setitem__(self, key, value):
self.data[key] = value
def published(self, repo) -> None:
self.data[repo.name] = datetime.datetime.now()
self.data_changed = True
def should_repo_publish(self, repo) -> bool:
if repo.name not in self.data:
return True
now_diff = datetime.datetime.now() - self.data[repo.name]
lifetime = convert_lifetime(repo.ipns_lifetime)
return now_diff >= (lifetime - ACCEPTED_DELTA_FOR_PUBLISHING)
def save(self):
if self.path is None:
logging.warning('No path specified for PublishingDatabase to save the db to')
return
with self.path.open('wb') as f:
pickle.dump(self.data, f)
self.data_changed = False
@classmethod
def load(cls):
path = get_data_dir() / 'republishing_db.pickle'
if not path.exists():
return cls({}, path)
logging.info(f'Loading published database from: {path}')
with path.open('rb') as f:
data = pickle.load(f)
logging.debug(f'Loaded data: {data}')
return cls(data, path)
def get_data_dir():
path = pathlib.Path(appdirs.user_data_dir(APP_NAME))
path.mkdir(parents=True, exist_ok=True)
return path
def convert_lifetime(value: str) -> datetime.timedelta:
if re.match(LIFETIME_SYNTAX_CHECK_REGEX, value, re.IGNORECASE) is None:
raise exceptions.PublishingException('Unknown lifetime syntax!')
matches = re.findall(LIFETIME_SYNTAX_REGEX, value, re.IGNORECASE)
base = datetime.timedelta()
for match in matches:
unit = LIFETIME_MAPPING[match[1].lower()]
base += datetime.timedelta(**{unit: int(match[0])})
return base
def republishing(db: PublishingDatabase, iterations: typing.Optional[int] = None):
config = config_module.Config.get_instance()
while iterations is None or iterations > 0:
for repo in config.repos.values():
if repo.republish and db.should_repo_publish(repo):
logger.info(f'Publishing repo {repo.name}')
repo.publish_name()
db.published(repo)
if db.data_changed:
db.save()
time.sleep(SLEEP_BETWEEN_CHECKS)
if iterations is not None:
iterations -= 1
def start_publishing():
try:
db = PublishingDatabase.load()
republishing(db)
except KeyboardInterrupt:
logging.info('Received SIGINT, terminating republisher service')
if __name__ == '__main__':
start_publishing()