2014-03-10 05:18:05 +00:00
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of Sick Beard.
#
# Sick Beard is free software: you can redistribute it and/or modify
# 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.
#
# Sick Beard is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>.
import time
import datetime
import sqlite3
import sickbeard
from sickbeard import db
from sickbeard import logger
from sickbeard . common import Quality
from sickbeard import helpers , show_name_helpers
from sickbeard import name_cache
from sickbeard . exceptions import MultipleShowObjectsException , ex
from sickbeard . exceptions import ex , AuthException
try :
import xml . etree . cElementTree as etree
except ImportError :
import elementtree . ElementTree as etree
from sickbeard . indexers import indexer_api , indexer_exceptions
from name_parser . parser import NameParser , InvalidNameException
class CacheDBConnection ( db . DBConnection ) :
def __init__ ( self , providerName ) :
db . DBConnection . __init__ ( self , " cache.db " )
# Create the table if it's not already there
try :
sql = " CREATE TABLE [ " + providerName + " ] (name TEXT, season NUMERIC, episodes TEXT, indexerid NUMERIC, url TEXT, time NUMERIC, quality TEXT); "
self . connection . execute ( sql )
self . connection . commit ( )
except sqlite3 . OperationalError , e :
if str ( e ) != " table [ " + providerName + " ] already exists " :
raise
# Create the table if it's not already there
try :
sql = " CREATE TABLE lastUpdate (provider TEXT, time NUMERIC); "
self . connection . execute ( sql )
self . connection . commit ( )
except sqlite3 . OperationalError , e :
if str ( e ) != " table lastUpdate already exists " :
raise
class TVCache ( ) :
def __init__ ( self , provider ) :
self . provider = provider
self . providerID = self . provider . getID ( )
self . minTime = 10
def _getDB ( self ) :
return CacheDBConnection ( self . providerID )
def _clearCache ( self ) :
myDB = self . _getDB ( )
myDB . action ( " DELETE FROM [ " + self . providerID + " ] WHERE 1 " )
def _getRSSData ( self ) :
data = None
return data
def _checkAuth ( self , parsedXML ) :
return True
def _checkItemAuth ( self , title , url ) :
return True
def updateCache ( self ) :
if not self . shouldUpdate ( ) :
return
if self . _checkAuth ( None ) :
data = self . _getRSSData ( )
# as long as the http request worked we count this as an update
if data :
self . setLastUpdate ( )
else :
return [ ]
# now that we've loaded the current RSS feed lets delete the old cache
logger . log ( u " Clearing " + self . provider . name + " cache and updating with new information " )
self . _clearCache ( )
parsedXML = helpers . parse_xml ( data )
if parsedXML is None :
logger . log ( u " Error trying to load " + self . provider . name + " RSS feed " , logger . ERROR )
return [ ]
if self . _checkAuth ( parsedXML ) :
if parsedXML . tag == ' rss ' :
items = parsedXML . findall ( ' .//item ' )
else :
logger . log ( u " Resulting XML from " + self . provider . name + " isn ' t RSS, not parsing it " , logger . ERROR )
return [ ]
cl = [ ]
for item in items :
ci = self . _parseItem ( item )
if ci is not None :
cl . append ( ci )
if len ( cl ) > 0 :
myDB = self . _getDB ( )
myDB . mass_action ( cl )
else :
raise AuthException ( u " Your authentication credentials for " + self . provider . name + " are incorrect, check your config " )
return [ ]
def _translateTitle ( self , title ) :
return title . replace ( ' ' , ' . ' )
def _translateLinkURL ( self , url ) :
return url . replace ( ' & ' , ' & ' )
def _parseItem ( self , item ) :
title = helpers . get_xml_text ( item . find ( ' title ' ) )
url = helpers . get_xml_text ( item . find ( ' link ' ) )
self . _checkItemAuth ( title , url )
if title and url :
title = self . _translateTitle ( title )
url = self . _translateLinkURL ( url )
logger . log ( u " Adding item from RSS to cache: " + title , logger . DEBUG )
return self . _addCacheEntry ( title , url )
else :
logger . log ( u " The XML returned from the " + self . provider . name + " feed is incomplete, this result is unusable " , logger . DEBUG )
return None
def _getLastUpdate ( self ) :
myDB = self . _getDB ( )
sqlResults = myDB . select ( " SELECT time FROM lastUpdate WHERE provider = ? " , [ self . providerID ] )
if sqlResults :
lastTime = int ( sqlResults [ 0 ] [ " time " ] )
2014-03-11 20:22:00 +00:00
if lastTime > int ( time . mktime ( datetime . datetime . today ( ) . timetuple ( ) ) ) :
lastTime = 0
2014-03-10 05:18:05 +00:00
else :
lastTime = 0
return datetime . datetime . fromtimestamp ( lastTime )
def setLastUpdate ( self , toDate = None ) :
if not toDate :
toDate = datetime . datetime . today ( )
myDB = self . _getDB ( )
myDB . upsert ( " lastUpdate " ,
{ ' time ' : int ( time . mktime ( toDate . timetuple ( ) ) ) } ,
{ ' provider ' : self . providerID } )
lastUpdate = property ( _getLastUpdate )
def shouldUpdate ( self ) :
# if we've updated recently then skip the update
if datetime . datetime . today ( ) - self . lastUpdate < datetime . timedelta ( minutes = self . minTime ) :
logger . log ( u " Last update was too soon, using old cache: today()- " + str ( self . lastUpdate ) + " < " + str ( datetime . timedelta ( minutes = self . minTime ) ) , logger . DEBUG )
return False
return True
2014-03-11 16:35:13 +00:00
def _addCacheEntry ( self , name , url , season = None , episodes = None , indexer_id = 0 , quality = None , extraNames = [ ] ) :
2014-03-10 05:18:05 +00:00
myDB = self . _getDB ( )
parse_result = None
# if we don't have complete info then parse the filename to get it
for curName in [ name ] + extraNames :
try :
myParser = NameParser ( )
parse_result = myParser . parse ( curName , True )
except InvalidNameException :
logger . log ( u " Unable to parse the filename " + curName + " into a valid episode " , logger . DEBUG )
continue
if not parse_result :
logger . log ( u " Giving up because I ' m unable to parse this name: " + name , logger . DEBUG )
return None
if not parse_result . series_name :
logger . log ( u " No series name retrieved from " + name + " , unable to cache it " , logger . DEBUG )
return None
indexer_lang = None
2014-03-11 16:35:13 +00:00
# if we need indexer_id then search the DB for them
2014-03-10 20:31:41 +00:00
if not indexer_id :
2014-03-10 05:18:05 +00:00
# if we have only the indexer_id, use the database
if indexer_id :
try :
showObj = helpers . findCertainShow ( sickbeard . showList , indexer_id )
except ( MultipleShowObjectsException ) :
showObj = None
if showObj :
indexer_lang = showObj . lang
# if they're both empty then fill out as much info as possible by searching the show name
else :
# check the name cache and see if we already know what show this is
2014-03-11 02:32:02 +00:00
logger . log ( u " Checking the cache to see if we already know the indexer id of " + parse_result . series_name , logger . DEBUG )
2014-03-10 05:18:05 +00:00
indexer_id = name_cache . retrieveNameFromCache ( parse_result . series_name )
# remember if the cache lookup worked or not so we know whether we should bother updating it later
if indexer_id == None :
logger . log ( u " No cache results returned, continuing on with the search " , logger . DEBUG )
from_cache = False
else :
logger . log ( u " Cache lookup found " + repr ( indexer_id ) + " , using that " , logger . DEBUG )
from_cache = True
# if the cache failed, try looking up the show name in the database
if indexer_id == None :
logger . log ( u " Trying to look the show up in the show database " , logger . DEBUG )
showResult = helpers . searchDBForShow ( parse_result . series_name )
if showResult :
2014-03-10 07:01:46 +00:00
logger . log ( parse_result . series_name + " was found to be show " + showResult [ 2 ] + " ( " + str ( showResult [ 1 ] ) + " ) in our DB. " , logger . DEBUG )
indexer_id = showResult [ 1 ]
2014-03-10 05:18:05 +00:00
# if the DB lookup fails then do a comprehensive regex search
if indexer_id == None :
logger . log ( u " Couldn ' t figure out a show name straight from the DB, trying a regex search instead " , logger . DEBUG )
for curShow in sickbeard . showList :
if show_name_helpers . isGoodResult ( name , curShow , False ) :
logger . log ( u " Successfully matched " + name + " to " + curShow . name + " with regex " , logger . DEBUG )
indexer_id = curShow . indexerid
indexer_lang = curShow . lang
break
# if indexer_id was anything but None (0 or a number) then
if not from_cache :
name_cache . addNameToCache ( parse_result . series_name , indexer_id )
# if we came out with indexer_id = None it means we couldn't figure it out at all, just use 0 for that
if indexer_id == None :
indexer_id = 0
# if we found the show then retrieve the show object
if indexer_id :
try :
showObj = helpers . findCertainShow ( sickbeard . showList , indexer_id )
except ( MultipleShowObjectsException ) :
showObj = None
if showObj :
2014-03-12 05:30:49 +00:00
self . indexer = showObj . indexer
2014-03-10 05:18:05 +00:00
indexer_lang = showObj . lang
# if we weren't provided with season/episode information then get it from the name that we parsed
if not season :
season = parse_result . season_number if parse_result . season_number != None else 1
if not episodes :
episodes = parse_result . episode_numbers
# if we have an air-by-date show then get the real season/episode numbers
if parse_result . air_by_date and indexer_id :
try :
2014-03-12 05:28:30 +00:00
lINDEXER_API_PARMS = { ' indexer ' : self . indexer }
2014-03-10 05:18:05 +00:00
if not ( indexer_lang == " " or indexer_lang == " en " or indexer_lang == None ) :
lINDEXER_API_PARMS [ ' language ' ] = indexer_lang
t = indexer_api . indexerApi ( * * lINDEXER_API_PARMS )
2014-03-12 05:28:30 +00:00
2014-03-10 05:18:05 +00:00
epObj = t [ indexer_id ] . airedOn ( parse_result . air_date ) [ 0 ]
season = int ( epObj [ " seasonnumber " ] )
episodes = [ int ( epObj [ " episodenumber " ] ) ]
except indexer_exceptions . indexer_episodenotfound :
logger . log ( u " Unable to find episode with date " + str ( parse_result . air_date ) + " for show " + parse_result . series_name + " , skipping " , logger . WARNING )
return None
except indexer_exceptions . indexer_error , e :
logger . log ( u " Unable to contact " + self . indexer + " : " + ex ( e ) , logger . WARNING )
return None
episodeText = " | " + " | " . join ( map ( str , episodes ) ) + " | "
# get the current timestamp
curTimestamp = int ( time . mktime ( datetime . datetime . today ( ) . timetuple ( ) ) )
if not quality :
quality = Quality . sceneQuality ( name )
if not isinstance ( name , unicode ) :
name = unicode ( name , ' utf-8 ' )
myDB . action ( " INSERT INTO [ " + self . providerID + " ] (name, season, episodes, indexerid, url, time, quality) VALUES (?,?,?,?,?,?,?) " ,
[ name , season , episodeText , indexer_id , url , curTimestamp , quality ] )
def searchCache ( self , episode , manualSearch = False ) :
neededEps = self . findNeededEpisodes ( episode , manualSearch )
return neededEps [ episode ]
def listPropers ( self , date = None , delimiter = " . " ) :
myDB = self . _getDB ( )
sql = " SELECT * FROM [ " + self . providerID + " ] WHERE name LIKE ' % .PROPER. % ' OR name LIKE ' % .REPACK. % ' "
if date != None :
sql + = " AND time >= " + str ( int ( time . mktime ( date . timetuple ( ) ) ) )
#return filter(lambda x: x['indexerid'] != 0, myDB.select(sql))
return myDB . select ( sql )
def findNeededEpisodes ( self , episode = None , manualSearch = False ) :
neededEps = { }
if episode :
neededEps [ episode ] = [ ]
myDB = self . _getDB ( )
if not episode :
sqlResults = myDB . select ( " SELECT * FROM [ " + self . providerID + " ] " )
else :
sqlResults = myDB . select ( " SELECT * FROM [ " + self . providerID + " ] WHERE indexerid = ? AND season = ? AND episodes LIKE ? " , [ episode . show . indexerid , episode . season , " % | " + str ( episode . episode ) + " | % " ] )
# for each cache entry
for curResult in sqlResults :
# skip non-tv crap (but allow them for Newzbin cause we assume it's filtered well)
if self . providerID != ' newzbin ' and not show_name_helpers . filterBadReleases ( curResult [ " name " ] ) :
continue
# get the show object, or if it's not one of our shows then ignore it
try :
showObj = helpers . findCertainShow ( sickbeard . showList , int ( curResult [ " indexerid " ] ) )
except ( MultipleShowObjectsException ) :
showObj = None
if not showObj :
continue
# get season and ep data (ignoring multi-eps for now)
curSeason = int ( curResult [ " season " ] )
if curSeason == - 1 :
continue
curEp = curResult [ " episodes " ] . split ( " | " ) [ 1 ]
if not curEp :
continue
curEp = int ( curEp )
curQuality = int ( curResult [ " quality " ] )
# if the show says we want that episode then add it to the list
if not showObj . wantEpisode ( curSeason , curEp , curQuality , manualSearch ) :
logger . log ( u " Skipping " + curResult [ " name " ] + " because we don ' t want an episode that ' s " + Quality . qualityStrings [ curQuality ] , logger . DEBUG )
else :
if episode :
epObj = episode
else :
epObj = showObj . getEpisode ( curSeason , curEp )
# build a result object
title = curResult [ " name " ]
url = curResult [ " url " ]
logger . log ( u " Found result " + title + " at " + url )
result = self . provider . getResult ( [ epObj ] )
result . url = url
result . name = title
result . quality = curQuality
result . content = self . provider . getURL ( url ) \
if self . provider . providerType == sickbeard . providers . generic . GenericProvider . TORRENT \
and not url . startswith ( ' magnet ' ) else None
# add it to the list
if epObj not in neededEps :
neededEps [ epObj ] = [ result ]
else :
neededEps [ epObj ] . append ( result )
return neededEps