-
Notifications
You must be signed in to change notification settings - Fork 278
/
show_updater.py
277 lines (238 loc) · 13.7 KB
/
show_updater.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# coding=utf-8
"""Show updater module."""
from __future__ import unicode_literals
import logging
import threading
import time
from builtins import object
from medusa import app, db, network_timezones, ui
from medusa.helper.exceptions import CantRefreshShowException, CantUpdateShowException
from medusa.indexers.api import indexerApi
from medusa.indexers.exceptions import IndexerException, IndexerSeasonUpdatesNotSupported, IndexerShowUpdatesNotSupported, IndexerUnavailable
from medusa.scene_exceptions import refresh_exceptions_cache
from medusa.session.core import MedusaSession
from requests.exceptions import HTTPError, RequestException
logger = logging.getLogger(__name__)
class ShowUpdater(object):
"""Show updater class."""
def __init__(self):
"""Show updatere constructor."""
self.lock = threading.Lock()
self.amActive = False
self.session = MedusaSession()
self.update_cache = UpdateCache()
def run(self, force=False):
"""Start show updater."""
self.amActive = True
refresh_shows = [] # A list of shows, that need to be refreshed
season_updates = [] # A list of show seasons that have passed their next_update timestamp
update_max_weeks = 12
network_timezones.update_network_dict()
# Refresh the exceptions_cache from db.
refresh_exceptions_cache()
logger.info('Started periodic show updates')
# Cache for the indexers list of updated show
indexer_updated_shows = {}
# Cache for the last indexer update timestamp
last_updates = {}
# Loop through the list of shows, and per show evaluate if we can use the .get_last_updated_seasons()
for show in app.showList:
show_updates_supported = True
if show.paused:
logger.info('The show {show} is paused, not updating it.', show=show.name)
continue
indexer_name = indexerApi(show.indexer).name
indexer_api_params = indexerApi(show.indexer).api_params.copy()
try:
indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params)
except IndexerUnavailable:
logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
'connectivity issues. While trying to look for show updates on show: {show}',
indexer_name=indexer_name, show=show.name)
continue
# Get the lastUpdate timestamp for this indexer.
if indexer_name not in last_updates:
last_indexer_update = self.update_cache.get_last_indexer_update(indexer_name)
if not last_indexer_update:
last_updates[indexer_name] = int(time.time() - 86400) # 1 day ago
elif last_indexer_update < time.time() - 604800 * update_max_weeks:
last_updates[indexer_name] = int(time.time() - 604800) # 1 week ago
else:
last_updates[indexer_name] = last_indexer_update
# Get a list of updated shows from the indexer, since last update.
if show.indexer not in indexer_updated_shows:
try:
indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series(
last_updates[indexer_name], update_max_weeks
)
except IndexerShowUpdatesNotSupported:
logger.info('Could not get a list with updated shows from indexer {indexer_name},'
' as this is not supported. Attempting a regular update for show: {show}',
indexer_name=indexer_name, show=show.name)
show_updates_supported = False
except IndexerUnavailable:
logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
'connectivity issues while trying to look for show updates on show: {show}',
indexer_name=indexer_name, show=show.name)
continue
except IndexerException as error:
logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
'issues while trying to get updates for show {show}. Cause: {cause!r}',
indexer_name=indexer_name, show=show.name, cause=error)
continue
except RequestException as error:
logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
'issues while trying to get updates for show {show}. Cause: {cause!r}',
indexer_name=indexer_name, show=show.name, cause=error)
if isinstance(error, HTTPError):
if error.response.status_code == 503:
logger.warning('API Service offline: '
'This service is temporarily offline, try again later.')
elif error.response.status_code == 429:
logger.warning('Your request count (#) is over the allowed limit of (40).')
continue
except Exception as error:
logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having '
'issues while trying to get updates for show {show}. Cause: {cause!r}.',
indexer_name=indexer_name, show=show.name, cause=error)
continue
# Update shows that were updated in the last X weeks
# or were not updated within the last X weeks
if show.indexerid not in indexer_updated_shows.get(show.indexer, []) and show_updates_supported:
if show.last_update_indexer > time.time() - 604800 * update_max_weeks:
logger.debug('Skipping show update for {show}. Show was not in the '
'indexers {indexer_name} list with updated shows and it '
'was updated within the last {weeks} weeks.', show=show.name,
indexer_name=indexer_name, weeks=update_max_weeks)
continue
try:
# Get updated seasons and add them to the season update list.
try:
updated_seasons = indexer_api.get_last_updated_seasons(
[show.indexerid], from_time=show.last_update_indexer, weeks=update_max_weeks, cache=self.update_cache
)
except IndexerSeasonUpdatesNotSupported:
raise
except IndexerUnavailable:
logger.warning('Problem get_last_updated_seasonsrunning show_updater, Indexer {indexer_name} seems to be having '
'connectivity issues while trying to look for show updates for show: {show}',
indexer_name=indexer_name, show=show.name)
continue
except IndexerException as error:
logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
'issues while trying to get updates for show {show}. Cause: {cause!r}',
indexer_name=indexer_name, show=show.name, cause=error)
continue
except Exception as error:
logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having '
'issues while trying to get updates for show {show}. Cause: {cause!r}',
indexer_name=indexer_name, show=show.name, cause=error)
continue
if updated_seasons[show.indexerid]:
logger.info('{show_name}: Adding the following seasons for update to queue: {seasons}',
show_name=show.name, seasons=updated_seasons[show.indexerid])
season_updates.append((show.indexer, show, updated_seasons[show.indexerid]))
elif show.indexerid in indexer_updated_shows.get(show.indexer, []):
# This show was marked to have an update, but it didn't get a season update. Let's fully
# update the show anyway.
logger.debug('Could not detect a season update, but an update is required. \n'
'Adding the following show for full update to queue: {show}', show=show.name)
refresh_shows.append(show)
except IndexerSeasonUpdatesNotSupported:
logger.debug('Adding the following show for full update to queue: {show}', show=show.name)
refresh_shows.append(show)
pi_list = []
# Full refreshes
for show in refresh_shows:
logger.info('Full update on show: {show}', show=show.name)
try:
pi_list.append(app.show_queue_scheduler.action.updateShow(show))
except (CantUpdateShowException, CantRefreshShowException) as e:
logger.warning('Automatic update failed. Error: {error}', error=e)
except Exception as e:
logger.error('Automatic update failed: Error: {error}', error=e)
# Only update expired season
for show in season_updates:
logger.info('Updating season {season} for show: {show}.', season=show[2], show=show[1].name)
try:
pi_list.append(app.show_queue_scheduler.action.updateShow(show[1], season=show[2]))
except CantUpdateShowException as e:
logger.warning('Automatic update failed. Error: {error}', error=e)
except Exception as e:
logger.error('Automatic update failed: Error: {error}', error=e)
ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))
# Only refresh updated shows that have been updated using the season updates.
# The full refreshed shows, are updated from the queueItem.
for show in set(show[1] for show in season_updates):
try:
app.show_queue_scheduler.action.refreshShow(show, True)
except CantRefreshShowException as e:
logger.warning('Show refresh on show {show_name} failed. Error: {error}',
show_name=show.name, error=e)
except Exception as e:
logger.error('Show refresh on show {show_name} failed: Unexpected Error: {error}',
show_name=show.name, error=e)
if refresh_shows or season_updates:
for indexer in set([s.indexer for s in refresh_shows] + [s[1].indexer for s in season_updates]):
indexer_api = indexerApi(indexer)
self.update_cache.set_last_indexer_update(indexer_api.name)
logger.info('Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name)
logger.info('Completed scheduling updates on shows')
else:
logger.info('Completed scheduling updates on shows, but there was nothing to update')
self.amActive = False
class UpdateCache(db.DBConnection):
"""Show updater update cache class."""
def __init__(self):
"""Show updater update cache constructor."""
super(UpdateCache, self).__init__('cache.db')
def get_last_indexer_update(self, indexer):
"""Get the last update timestamp from the lastUpdate table.
:param indexer:
:type indexer: Indexer name from indexer_config's name attribute.
:return: epoch timestamp
:rtype: int
"""
last_indexer_update = self.select(
'SELECT time '
'FROM lastUpdate '
'WHERE provider = ?',
[indexer]
)
return last_indexer_update[0]['time'] if last_indexer_update else None
def set_last_indexer_update(self, indexer):
"""Set the last update timestamp from the lastUpdate table.
:param indexer:
:type indexer: string, name respresentation, like 'theTVDB'. Check the indexer_config's name attribute.
:return: epoch timestamp
:rtype: int
"""
return self.upsert('lastUpdate',
{'time': int(time.time())},
{'provider': indexer})
def get_last_update_season(self, indexer, series_id, season):
"""Get the last update timestamp from the season_updates table.
:param indexer: Indexer id. 1 for TheTvdb.
:type indexer: int
:param series: Indexers series id.
:type indexer: int
:param season: Season
"""
last_season_update = self.select(
'SELECT time '
'FROM season_updates '
'WHERE indexer = ? AND series_id = ? AND season = ?',
[indexer, series_id, season]
)
return last_season_update[0]['time'] if last_season_update else 0
def set_last_update_season(self, indexer, series_id, season):
"""Set the last update timestamp from the season_updates table.
:param indexer: Indexer id. 1 for TheTvdb.
:type indexer: int
:param series: Indexers series id.
:type indexer: int
:param season: Season
"""
return self.upsert('season_updates',
{'time': int(time.time())},
{'indexer': indexer, 'series_id': series_id, 'season': season})