tv_cache.py 11.7 KB
Newer Older
1
# Author: echel0n <[email protected]>
echel0n's avatar
echel0n committed
2
# URL: https://sickrage.ca
3
#
echel0n's avatar
echel0n committed
4
# This file is part of SickRage.
5
#
echel0n's avatar
echel0n committed
6
# SickRage is free software: you can redistribute it and/or modify
7
8
9
10
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
echel0n's avatar
echel0n committed
11
# SickRage is distributed in the hope that it will be useful,
12
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
echel0n's avatar
echel0n committed
14
# GNU General Public License for more details.
15
16
#
# You should have received a copy of the GNU General Public License
echel0n's avatar
echel0n committed
17
# along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
18

19
20
from __future__ import unicode_literals

21
import datetime
22
import time
23

echel0n's avatar
echel0n committed
24
import feedparser
echel0n's avatar
echel0n committed
25
from CodernityDB.database import RecordNotFound
echel0n's avatar
v8.8.5    
echel0n committed
26
27

import sickrage
28
29
30
from sickrage.core.common import Quality
from sickrage.core.exceptions import AuthException
from sickrage.core.helpers import findCertainShow, show_names
31
from sickrage.core.nameparser import InvalidNameException, NameParser, InvalidShowException
echel0n's avatar
echel0n committed
32
from sickrage.core.websession import WebSession
33

echel0n's avatar
echel0n committed
34

Dustyn Gibson's avatar
Dustyn Gibson committed
35
class TVCache(object):
echel0n's avatar
echel0n committed
36
    def __init__(self, provider, min_time=10, search_params=None):
37
        self.provider = provider
38
        self.providerID = self.provider.id
echel0n's avatar
echel0n committed
39
40
        self.min_time = min_time
        self.search_params = search_params or {'RSS': ['']}
41

echel0n's avatar
echel0n committed
42
    def clear(self):
echel0n's avatar
echel0n committed
43
        if self.shouldClearCache():
44
            [sickrage.app.cache_db.delete(x) for x in
45
             sickrage.app.cache_db.get_many('providers', self.providerID)]
46

47
48
49
    def _get_title_and_url(self, item):
        return self.provider._get_title_and_url(item)

echel0n's avatar
echel0n committed
50
51
52
53
54
55
    def _get_result_stats(self, item):
        return self.provider._get_result_stats(item)

    def _get_size(self, item):
        return self.provider._get_size(item)

echel0n's avatar
echel0n committed
56
    def _get_rss_data(self):
echel0n's avatar
echel0n committed
57
58
        if self.search_params:
            return {'entries': self.provider.search(self.search_params)}
59

60
    def _check_auth(self, data):
61
        return True
62

echel0n's avatar
echel0n committed
63
    def check_item(self, title, url):
64
65
        return True

echel0n's avatar
echel0n committed
66
    def update(self):
67
        # check if we should update
echel0n's avatar
echel0n committed
68
        if self.should_update():
69
            try:
echel0n's avatar
echel0n committed
70
                data = self._get_rss_data()
71
                if not self._check_auth(data):
72
                    return False
73

74
                # clear cache
echel0n's avatar
echel0n committed
75
                self.clear()
76
77

                # set updated
78
                self.last_update = datetime.datetime.today()
79

echel0n's avatar
echel0n committed
80
                [self._parseItem(item) for item in data['entries']]
81
            except AuthException as e:
82
                sickrage.app.log.warning("Authentication error: {}".format(e.message))
83
84
                return False
            except Exception as e:
85
                sickrage.app.log.debug(
echel0n's avatar
echel0n committed
86
                    "Error while searching {}, skipping: {}".format(self.provider.name, repr(e)))
87
                return False
88

89
        return True
90

91
    def getRSSFeed(self, url, params=None):
echel0n's avatar
echel0n committed
92
93
94
95
96
97
98
99
        try:
            if self.provider.login():
                resp = WebSession().get(url, params=params).text
                return feedparser.parse(resp)
        except Exception as e:
            sickrage.app.log.debug("RSS Error: {}".format(e.message))

        return feedparser.FeedParserDict()
100
101

    def _translateTitle(self, title):
102
        return '' + title.replace(' ', '.')
103
104
105
106

    def _translateLinkURL(self, url):
        return url.replace('&amp;', '&')

107
108
    def _parseItem(self, item):
        title, url = self._get_title_and_url(item)
echel0n's avatar
echel0n committed
109
110
        seeders, leechers = self._get_result_stats(item)
        size = self._get_size(item)
echel0n's avatar
echel0n committed
111

echel0n's avatar
echel0n committed
112
        self.check_item(title, url)
113
114

        if title and url:
115
            self.addCacheEntry(self._translateTitle(title), self._translateLinkURL(url), seeders, leechers, size)
116
        else:
117
            sickrage.app.log.debug(
echel0n's avatar
echel0n committed
118
                "The data returned from the " + self.provider.name + " feed is incomplete, this result is unusable")
119

120
121
    @property
    def last_update(self):
echel0n's avatar
echel0n committed
122
        try:
123
            dbData = sickrage.app.cache_db.get('lastUpdate', self.providerID)
echel0n's avatar
echel0n committed
124
            lastTime = int(dbData["time"])
echel0n's avatar
echel0n committed
125
            if lastTime > int(time.mktime(datetime.datetime.today().timetuple())): lastTime = 0
echel0n's avatar
echel0n committed
126
        except RecordNotFound:
127
128
            lastTime = 0

129
        return datetime.datetime.fromtimestamp(lastTime)
130

131
    @last_update.setter
132
    def last_update(self, toDate):
echel0n's avatar
echel0n committed
133
        try:
134
            dbData = sickrage.app.cache_db.get('lastUpdate', self.providerID)
echel0n's avatar
echel0n committed
135
            dbData['time'] = int(time.mktime(toDate.timetuple()))
136
            sickrage.app.cache_db.update(dbData)
echel0n's avatar
echel0n committed
137
        except RecordNotFound:
138
            sickrage.app.cache_db.insert({
echel0n's avatar
echel0n committed
139
140
141
142
                '_t': 'lastUpdate',
                'provider': self.providerID,
                'time': int(time.mktime(toDate.timetuple()))
            })
143

144
145
146
    @property
    def last_search(self):
        try:
147
            dbData = sickrage.app.cache_db.get('lastSearch', self.providerID)
148
149
150
151
152
153
154
            lastTime = int(dbData["time"])
            if lastTime > int(time.mktime(datetime.datetime.today().timetuple())): lastTime = 0
        except RecordNotFound:
            lastTime = 0

        return datetime.datetime.fromtimestamp(lastTime)

155
    @last_search.setter
156
    def last_search(self, toDate):
echel0n's avatar
echel0n committed
157
        try:
158
            dbData = sickrage.app.cache_db.get('lastSearch', self.providerID)
echel0n's avatar
echel0n committed
159
            dbData['time'] = int(time.mktime(toDate.timetuple()))
160
            sickrage.app.cache_db.update(dbData)
echel0n's avatar
echel0n committed
161
        except RecordNotFound:
162
            sickrage.app.cache_db.insert({
echel0n's avatar
echel0n committed
163
164
165
166
                '_t': 'lastUpdate',
                'provider': self.providerID,
                'time': int(time.mktime(toDate.timetuple()))
            })
167

echel0n's avatar
echel0n committed
168
    def should_update(self):
echel0n's avatar
echel0n committed
169
        if sickrage.app.developer: return True
echel0n's avatar
v9.0.35    
echel0n committed
170

171
        # if we've updated recently then skip the update
echel0n's avatar
echel0n committed
172
        if datetime.datetime.today() - self.last_update < datetime.timedelta(minutes=self.min_time):
echel0n's avatar
echel0n committed
173
            return False
174
175
        return True

176
177
    def shouldClearCache(self):
        # if daily search hasn't used our previous results yet then don't clear the cache
echel0n's avatar
echel0n committed
178
        if self.last_update > self.last_search:
179
180
            return False
        return True
181

182
    def addCacheEntry(self, name, url, seeders, leechers, size):
183
        # check for existing entry in cache
184
185
        if len([x for x in sickrage.app.cache_db.get_many('providers', self.providerID)
                if x['url'] == url]): return
186

187
188
189
        try:
            # parse release name
            parse_result = NameParser(validate_show=sickrage.app.config.enable_rss_cache_valid_shows).parse(name)
190
            if parse_result.series_name and parse_result.quality != Quality.UNKNOWN:
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
                season = parse_result.season_number if parse_result.season_number else 1
                episodes = parse_result.episode_numbers

                if season and episodes:
                    # store episodes as a seperated string
                    episodeText = "|" + "|".join(map(str, episodes)) + "|"

                    # get quality of release
                    quality = parse_result.quality

                    # get release group
                    release_group = parse_result.release_group

                    # get version
                    version = parse_result.version

                    dbData = {
                        '_t': 'providers',
                        'provider': self.providerID,
                        'name': name,
                        'season': season,
                        'episodes': episodeText,
                        'indexerid': parse_result.indexerid,
                        'url': url,
                        'time': int(time.mktime(datetime.datetime.today().timetuple())),
                        'quality': quality,
                        'release_group': release_group,
                        'version': version,
                        'seeders': seeders,
                        'leechers': leechers,
                        'size': size
                    }

                    # add to internal database
225
                    sickrage.app.cache_db.insert(dbData)
226
227
228
229
230
231
232
233
234
235
236

                    # add to external database
                    if sickrage.app.config.enable_api_providers_cache and not self.provider.private:
                        try:
                            sickrage.app.api.add_cache_result(dbData)
                        except Exception:
                            pass

                    sickrage.app.log.debug("SEARCH RESULT:[%s] ADDED TO CACHE!", name)
        except (InvalidShowException, InvalidNameException):
            pass
echel0n's avatar
echel0n committed
237

238
    def search_cache(self, ep_obj=None, manualSearch=False, downCurQuality=False):
239
        neededEps = {}
240
        dbData = []
241

242
        # get data from external database
243
        if sickrage.app.config.enable_api_providers_cache and not self.provider.private:
244
            try:
245
                dbData += sickrage.app.api.get_cache_results(self.providerID, ep_obj.show.indexerid)
246
247
            except Exception:
                pass
248
249

        # get data from internal database
250
        dbData += [x for x in sickrage.app.cache_db.get_many('providers', self.providerID)]
251

252
        # sort data by criteria
253
254
        dbData = [x for x in dbData if x['indexerid'] == ep_obj.show.indexerid and x['season'] == ep_obj.season
                  and "|" + str(ep_obj.episode) + "|" in x['episodes']] if ep_obj else dbData
255
256

        # for each cache entry
echel0n's avatar
echel0n committed
257
        for curResult in dbData:
echel0n's avatar
echel0n committed
258
259
            result = self.provider.getResult()

260
            # ignored/required words, and non-tv junk
261
            if not show_names.filterBadReleases(curResult["name"]):
262
263
                continue

264
            # get the show object, or if it's not one of our shows then ignore it
265
            showObj = findCertainShow(int(curResult["indexerid"]))
266
267
268
269
270
            if not showObj:
                continue

            # skip if provider is anime only and show is not anime
            if self.provider.anime_only and not showObj.is_anime:
271
                sickrage.app.log.debug("" + str(showObj.name) + " is not an anime, skiping")
272
273
274
                continue

            # get season and ep data (ignoring multi-eps for now)
275
            curSeason = int(curResult["season"])
276
277
            if curSeason == -1:
                continue
278

279
            curEp = curResult["episodes"].split("|")[1]
280
281
            if not curEp:
                continue
282

283
284
            curEp = int(curEp)

echel0n's avatar
echel0n committed
285
286
287
            result.quality = int(curResult["quality"])
            result.release_group = curResult["release_group"]
            result.version = curResult["version"]
288
289

            # if the show says we want that episode then add it to the list
echel0n's avatar
echel0n committed
290
            if not showObj.wantEpisode(curSeason, curEp, result.quality, manualSearch, downCurQuality):
291
                sickrage.app.log.info(
echel0n's avatar
echel0n committed
292
                    "Skipping " + curResult["name"] + " because we don't want an episode that's " +
echel0n's avatar
echel0n committed
293
                    Quality.qualityStrings[result.quality])
294
295
                continue

echel0n's avatar
echel0n committed
296
            result.episodes = [showObj.getEpisode(curSeason, curEp)]
297
298

            # build a result object
echel0n's avatar
echel0n committed
299
300
            result.name = curResult["name"]
            result.url = curResult["url"]
301

302
            sickrage.app.log.info("Found result " + result.name + " at " + result.url)
303
304

            result.show = showObj
echel0n's avatar
echel0n committed
305
306
307
            result.seeders = curResult.get("seeders", -1)
            result.leechers = curResult.get("leechers", -1)
            result.size = curResult.get("size", -1)
308
309
310
            result.content = None

            # add it to the list
echel0n's avatar
echel0n committed
311
312
            if result.episodes[0].episode not in neededEps:
                neededEps[result.episodes[0].episode] = [result]
313
            else:
echel0n's avatar
echel0n committed
314
                neededEps[result.episodes[0].episode] += [result]
315

316
        # datetime stamp this search so cache gets cleared
317
        self.last_search = datetime.datetime.today()
318

319
        return neededEps