Advertisement
Guest User

FreeNAS fix for btn.py

a guest
Feb 19th, 2013
160
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.83 KB | None | 0 0
  1. # coding=utf-8
  2. # Author: Daniël Heimans
  3. # URL: http://code.google.com/p/sickbeard
  4. #
  5. # This file is part of Sick Beard.
  6. #
  7. # Sick Beard is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Sick Beard is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
  19.  
  20. import sickbeard
  21. import generic
  22.  
  23. from sickbeard import scene_exceptions
  24. from sickbeard import logger
  25. from sickbeard import tvcache
  26. from sickbeard.helpers import sanitizeSceneName
  27. from sickbeard.common import Quality
  28. from sickbeard.exceptions import ex, AuthException
  29.  
  30. from lib import jsonrpclib
  31. import datetime
  32. import time
  33. import socket
  34. import math
  35. import pprint
  36.  
  37. class BTNProvider(generic.TorrentProvider):
  38.    
  39.     def __init__(self):
  40.        
  41.         generic.TorrentProvider.__init__(self, "BTN")
  42.        
  43.         self.supportsBacklog = True
  44.         self.cache = BTNCache(self)
  45.  
  46.         self.url = "http://broadcasthe.net"
  47.    
  48.     def isEnabled(self):
  49.         return sickbeard.BTN
  50.    
  51.     def imageName(self):
  52.         return 'btn.png'
  53.  
  54.     def checkAuthFromData(self, data):
  55.         result = True
  56.         if 'api-error' in data:
  57.             logger.log("Error in sickbeard data retrieval: " + data['api-error'], logger.ERROR)
  58.             result = False
  59.  
  60.         return result
  61.  
  62.     def _doSearch(self, search_params, show=None):
  63.         params = {}
  64.         apikey = sickbeard.BTN_API_KEY
  65.  
  66.         if search_params:
  67.             params.update(search_params)
  68.  
  69.         search_results = self._api_call(apikey, params)
  70.        
  71.         if not search_results:
  72.             return []
  73.  
  74.         if 'torrents' in search_results:
  75.             found_torrents = search_results['torrents']
  76.         else:
  77.             found_torrents = {}
  78.  
  79.         # We got something, we know the API sends max 1000 results at a time.
  80.         # See if there are more than 1000 results for our query, if not we
  81.         # keep requesting until we've got everything.
  82.         # max 150 requests per minute so limit at that
  83.         max_pages = 150
  84.         results_per_page = 1000.0
  85.  
  86.         if 'results' in search_results and search_results['results'] >= results_per_page:
  87.             pages_needed = int(math.ceil(int(search_results['results']) / results_per_page))
  88.             if pages_needed > max_pages:
  89.                 pages_needed = max_pages
  90.            
  91.             # +1 because range(1,4) = 1, 2, 3
  92.             for page in range(1,pages_needed+1):
  93.                 search_results = self._api_call(apikey, params, results_per_page, page * results_per_page)
  94.                 # Note that this these are individual requests and might time out individually. This would result in 'gaps'
  95.                 # in the results. There is no way to fix this though.
  96.                 if 'torrents' in search_results:
  97.                     found_torrents.update(search_results['torrents'])
  98.  
  99.         results = []
  100.  
  101.         for torrentid, torrent_info in found_torrents.iteritems():
  102.             (title, url) = self._get_title_and_url(torrent_info)
  103.  
  104.             if not title or not url:
  105.                 logger.log(u"The BTN provider did not return both a valid title and URL for search parameters: " + str(params) + " but returned " + str(torrent_info), logger.WARNING)
  106.             results.append(torrent_info)
  107.  
  108. #        Disabled this because it overspammed the debug log a bit too much
  109. #        logger.log(u'BTN provider returning the following results for search parameters: ' + str(params), logger.DEBUG)
  110. #        for result in results:
  111. #            (title, result) = self._get_title_and_url(result)
  112. #            logger.log(title, logger.DEBUG)
  113.            
  114.         return results
  115.  
  116.     def _api_call(self, apikey, params={}, results_per_page=1000, offset=0):
  117.         server = jsonrpclib.Server('http://api.btnapps.net')
  118.        
  119.         search_results ={}
  120.         try:
  121.             search_results = server.getTorrentsSearch(apikey, params, int(results_per_page), int(offset))
  122.         except jsonrpclib.jsonrpc.ProtocolError, error:
  123.             logger.log(u"JSON-RPC protocol error while accessing BTN API: " + ex(error), logger.ERROR)
  124.             search_results = {'api-error': ex(error)}
  125.             return search_results
  126.         except socket.timeout:
  127.             logger.log(u"Timeout while accessing BTN API", logger.WARNING)
  128.         except socket.error, error:
  129.             # Note that sometimes timeouts are thrown as socket errors
  130.             logger.log(u"Socket error while accessing BTN API: " + error[1], logger.ERROR)
  131.         except Exception, error:
  132.             errorstring = str(error)
  133.             if(errorstring.startswith('<') and errorstring.endswith('>')):
  134.                 errorstring = errorstring[1:-1]
  135.             logger.log(u"Unknown error while accessing BTN API: " + errorstring, logger.ERROR)
  136.  
  137.         return search_results
  138.  
  139.     def _get_title_and_url(self, search_result):
  140.        
  141.         # The BTN API gives a lot of information in response,
  142.         # however Sick Beard is built mostly around Scene or
  143.         # release names, which is why we are using them here.
  144.         if 'ReleaseName' in search_result and search_result['ReleaseName']:
  145.             title = search_result['ReleaseName']
  146.         else:
  147.             # If we don't have a release name we need to get creative
  148.             title = u''
  149.             if 'Series' in search_result:
  150.                 title += search_result['Series']
  151.             if 'GroupName' in search_result:
  152.                 title += '.' + search_result['GroupName'] if title else search_result['GroupName']
  153.             if 'Resolution' in search_result:
  154.                 title += '.' + search_result['Resolution'] if title else search_result['Resolution']
  155.             if 'Source' in search_result:
  156.                 title += '.' + search_result['Source'] if title else search_result['Source']
  157.             if 'Codec' in search_result:
  158.                 title += '.' + search_result['Codec'] if title else search_result['Codec']
  159.        
  160.         if 'DownloadURL' in search_result:
  161.             url = search_result['DownloadURL']
  162.             url = url.replace("\\", "")
  163.     else:
  164.             url = None
  165.  
  166.         return (title, url)
  167.  
  168.     def _get_season_search_strings(self, show, season=None):
  169.         if not show:
  170.             return [{}]
  171.  
  172.         search_params = []
  173.  
  174.         name_exceptions = scene_exceptions.get_scene_exceptions(show.tvdbid) + [show.name]
  175.         for name in name_exceptions:
  176.  
  177.             current_params = {}
  178.  
  179.             if show.tvdbid:
  180.                 current_params['tvdb'] = show.tvdbid
  181.             elif show.tvrid:
  182.                 current_params['tvrage'] = show.tvrid
  183.             else:
  184.                 # Search by name if we don't have tvdb or tvrage id
  185.                 current_params['series'] = sanitizeSceneName(name)
  186.  
  187.             if season != None:
  188.                 whole_season_params = current_params.copy()
  189.                 partial_season_params = current_params.copy()
  190.                 # Search for entire seasons: no need to do special things for air by date shows
  191.                 whole_season_params['category'] = 'Season'
  192.                 whole_season_params['name'] = 'Season ' + str(season)
  193.  
  194.                 search_params.append(whole_season_params)
  195.  
  196.                 # Search for episodes in the season
  197.                 partial_season_params['category'] = 'Episode'
  198.                
  199.                 if show.air_by_date:
  200.                     # Search for the year of the air by date show
  201.                     partial_season_params['name'] = str(season.split('-')[0])
  202.                 else:
  203.                     # Search for any result which has Sxx in the name
  204.                     partial_season_params['name'] = 'S%02d' % int(season)
  205.  
  206.                 search_params.append(partial_season_params)
  207.  
  208.             else:
  209.                 search_params.append(current_params)
  210.        
  211.         return search_params
  212.  
  213.     def _get_episode_search_strings(self, ep_obj):
  214.        
  215.         if not ep_obj:
  216.             return [{}]
  217.  
  218.         search_params = {'category':'Episode'}
  219.  
  220.         if ep_obj.show.tvdbid:
  221.             search_params['tvdb'] = ep_obj.show.tvdbid
  222.         elif ep_obj.show.tvrid:
  223.             search_params['tvrage'] = ep_obj.show.rid
  224.         else:
  225.             search_params['series'] = sanitizeSceneName(ep_obj.show_name)
  226.  
  227.         if ep_obj.show.air_by_date:
  228.             date_str = str(ep_obj.airdate)
  229.            
  230.             # BTN uses dots in dates, we just search for the date since that
  231.             # combined with the series identifier should result in just one episode
  232.             search_params['name'] = date_str.replace('-','.')
  233.  
  234.         else:
  235.             # Do a general name search for the episode, formatted like SXXEYY
  236.             search_params['name'] = "S%02dE%02d" % (ep_obj.season,ep_obj.episode)
  237.  
  238.         to_return = [search_params]
  239.  
  240.         # only do scene exceptions if we are searching by name
  241.         if 'series' in search_params:
  242.            
  243.             # add new query string for every exception
  244.             name_exceptions = scene_exceptions.get_scene_exceptions(ep_obj.show.tvdbid)
  245.             for cur_exception in name_exceptions:
  246.                
  247.                 # don't add duplicates
  248.                 if cur_exception == ep_obj.show.name:
  249.                     continue
  250.  
  251.                 # copy all other parameters before setting the show name for this exception
  252.                 cur_return = search_params.copy()
  253.                 cur_return['series'] = sanitizeSceneName(cur_exception)
  254.                 to_return.append(cur_return)
  255.  
  256.         return to_return
  257.  
  258.     def getQuality(self, item):
  259.         quality = None
  260.         (title,url) = self._get_title_and_url(item)
  261.         quality = Quality.nameQuality(title)
  262.  
  263.         return quality
  264.  
  265.     def _doGeneralSearch(self, search_string):
  266.         # 'search' looks as broad is it can find. Can contain episode overview and title for example,
  267.         # use with caution!
  268.         return self._doSearch({'search': search_string})
  269.  
  270. class BTNCache(tvcache.TVCache):
  271.    
  272.     def __init__(self, provider):
  273.         tvcache.TVCache.__init__(self, provider)
  274.        
  275.         # At least 15 minutes between queries
  276.         self.minTime = 15
  277.  
  278.     def updateCache(self):
  279.         if not self.shouldUpdate():
  280.             return
  281.        
  282.         data = self._getRSSData()
  283.  
  284.         # As long as we got something from the provider we count it as an update
  285.         if data:
  286.             self.setLastUpdate()
  287.         else:
  288.             return []
  289.        
  290.         logger.log(u"Clearing "+self.provider.name+" cache and updating with new information")
  291.         self._clearCache()
  292.  
  293.         if not self._checkAuth(data):
  294.             raise AuthException("Your authentication info for "+self.provider.name+" is incorrect, check your config")
  295.  
  296.         # By now we know we've got data and no auth errors, all we need to do is put it in the database
  297.         for item in data:
  298.             self._parseItem(item)
  299.  
  300.     def _getRSSData(self):
  301.         # Get the torrents uploaded since last check.
  302.         seconds_since_last_update = math.ceil(time.time() - time.mktime(self._getLastUpdate().timetuple()))
  303.  
  304.        
  305.         # default to 15 minutes
  306.         if seconds_since_last_update < 15*60:
  307.             seconds_since_last_update = 15*60
  308.  
  309.         # Set maximum to 24 hours of "RSS" data search, older things will need to be done through backlog
  310.         if seconds_since_last_update > 24*60*60:
  311.             logger.log(u"The last known successful \"RSS\" update on the BTN API was more than 24 hours ago (%i hours to be precise), only trying to fetch the last 24 hours!" %(int(seconds_since_last_update)//(60*60)), logger.WARNING)
  312.             seconds_since_last_update = 24*60*60
  313.  
  314.         age_string = "<=%i" % seconds_since_last_update  
  315.         search_params={'age': age_string}
  316.  
  317.         data = self.provider._doSearch(search_params)
  318.        
  319.         return data
  320.  
  321.     def _parseItem(self, item):
  322.         (title, url) = self.provider._get_title_and_url(item)
  323.        
  324.         if not title or not url:
  325.             logger.log(u"The result returned from the BTN regular search is incomplete, this result is unusable", logger.ERROR)
  326.             return
  327.         logger.log(u"Adding item from regular BTN search to cache: " + title, logger.DEBUG)
  328.  
  329.         self._addCacheEntry(title, url)
  330.  
  331.     def _checkAuth(self, data):
  332.         return self.provider.checkAuthFromData(data)
  333.  
  334. provider = BTNProvider()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement