diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 56680b0..d4b905a 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -61,6 +61,11 @@ from _23 import b64encodestring, filter_iter, list_items, map_list from six import iteritems, PY2, string_types import sg_helpers +# noinspection PyUnreachableCode +if False: + from typing import Dict, List + from .tv import TVShow + PID = None ENV = {} @@ -98,7 +103,8 @@ watchedStateQueueScheduler = None provider_ping_thread_pool = {} -showList = [] +showList = [] # type: List[TVShow] +showDict = {} # type: Dict[int, TVShow] UPDATE_SHOWS_ON_START = False SHOW_UPDATE_HOUR = 3 @@ -602,7 +608,7 @@ def initialize(console_logging=True): def init_stage_1(console_logging): # Misc - global showList, providerList, newznabProviderList, torrentRssProviderList, \ + global showList, showDict, providerList, newznabProviderList, torrentRssProviderList, \ WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, ADD_SHOWS_METALANG, \ CREATE_MISSING_SHOW_DIRS, SHOW_DIRS_WITH_DOTS, \ RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, TVINFO_DEFAULT, CONFIG_FILE, \ @@ -1383,6 +1389,7 @@ def init_stage_1(console_logging): '
Important: You must Shutdown SickGear before upgrading' showList = [] + showDict = {} def init_stage_2(): diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index f534bc4..bb10acb 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -53,7 +53,7 @@ import subliminal from lxml_etree import etree, is_lxml from send2trash import send2trash -from _23 import b64decodebytes, b64encodebytes, decode_bytes, DirEntry, filter_iter, filter_list, scandir +from _23 import b64decodebytes, b64encodebytes, decode_bytes, DirEntry, filter_iter, scandir from six import iteritems, PY2, string_types, text_type # noinspection PyUnresolvedReferences from six.moves import zip @@ -276,14 +276,15 @@ def remove_file(filepath, tree=False, prefix_failure='', log_level=logger.MESSAG def find_show_by_id( show_id, # type: Union[AnyStr, Dict[int, int], int] show_list=None, # type: Optional[List[TVShow]] - no_mapped_ids=True # type: bool + no_mapped_ids=True, # type: bool + check_multishow=False # type: bool ): - # type: (...) -> TVShow or MultipleShowObjectsException + # type: (...) -> Optional[TVShow] """ :param show_id: {indexer: id} or 'tvid_prodid'. :param show_list: (optional) TVShow objects list :param no_mapped_ids: don't check mapped ids - :return: TVShow object or MultipleShowObjectsException + :param check_multishow: check for multiple show matches """ results = [] if None is show_list: @@ -295,8 +296,7 @@ def find_show_by_id( if tvid_prodid_obj and no_mapped_ids: if None is tvid_prodid_obj.prodid: return None - sid_int = int(tvid_prodid_obj) - results = filter_list(lambda _show_obj: sid_int == _show_obj.sid_int, show_list) + return sickbeard.showDict.get(int(tvid_prodid_obj)) else: if tvid_prodid_obj: if None is tvid_prodid_obj.prodid: @@ -304,12 +304,21 @@ def find_show_by_id( show_id = tvid_prodid_obj.dict if isinstance(show_id, dict): - show_id = {k: v for k, v in iteritems(show_id) if 0 < v} if no_mapped_ids: - results = filter_list(lambda _show_obj: show_id == {_show_obj.tvid: _show_obj.prodid}, show_list) + sid_int_list = [sickbeard.tv.TVShow.create_sid(sk, sv) for sk, sv in iteritems(show_id) if 0 < sv + and sickbeard.tv.tvid_bitmask >= sk] + if check_multishow: + results = [sickbeard.showDict.get(_show_sid_id) for _show_sid_id in sid_int_list + if sickbeard.showDict.get(_show_sid_id)] + else: + for _show_sid_id in sid_int_list: + if _show_sid_id in sickbeard.showDict: + return sickbeard.showDict.get(_show_sid_id) + return None else: + show_id = {k: v for k, v in iteritems(show_id) if 0 < v} results = [_show_obj for k, v in iteritems(show_id) - for _show_obj in show_list if v == _show_obj.ids.get(k, {'id': 0})['id']] + for _show_obj in show_list if v == _show_obj.internal_ids.get(k, {'id': 0})['id']] num_shows = len(set(results)) if 1 == num_shows: diff --git a/sickbeard/history.py b/sickbeard/history.py index 2c32e7e..296ab20 100644 --- a/sickbeard/history.py +++ b/sickbeard/history.py @@ -206,7 +206,7 @@ def history_snatched_proper_fix(): for r in sql_result: show_obj = None try: - show_obj = helpers.find_show_by_id({int(r['tv_id']): int(r['prod_id'])}) + show_obj = helpers.find_show_by_id({int(r['tv_id']): int(r['prod_id'])}, check_multishow=True) except (BaseException, Exception): pass np = NameParser(False, show_obj=show_obj, testing=True) diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py index 921bd22..ce88092 100644 --- a/sickbeard/processTV.py +++ b/sickbeard/processTV.py @@ -212,7 +212,8 @@ class ProcessTVShow(object): ' ORDER BY rowid', [name]) if sql_result: try: - show_obj = helpers.find_show_by_id({int(sql_result[-1]['indexer']): int(sql_result[-1]['showid'])}) + show_obj = helpers.find_show_by_id({int(sql_result[-1]['indexer']): int(sql_result[-1]['showid'])}, + check_multishow=True) if hasattr(show_obj, 'name'): logger.log('Found Show: %s in snatch history for: %s' % (show_obj.name, name), logger.DEBUG) except MultipleShowObjectsException: diff --git a/sickbeard/properFinder.py b/sickbeard/properFinder.py index d2ebd6f..16e0db3 100644 --- a/sickbeard/properFinder.py +++ b/sickbeard/properFinder.py @@ -570,7 +570,8 @@ def get_needed_qualites(needed=None): if needed.all_needed: break try: - show_obj = helpers.find_show_by_id({int(cur_result['tv_id']): int(cur_result['prod_id'])}) + show_obj = helpers.find_show_by_id({int(cur_result['tv_id']): int(cur_result['prod_id'])}, + check_multishow=True) except MultipleShowObjectsException: continue if show_obj: @@ -612,7 +613,8 @@ def _recent_history(aired_since_shows, aired_since_anime): for cur_result in sql_result: try: - show_obj = helpers.find_show_by_id({int(cur_result['tv_id']): int(cur_result['prod_id'])}) + show_obj = helpers.find_show_by_id({int(cur_result['tv_id']): int(cur_result['prod_id'])}, + check_multishow=True) except MultipleShowObjectsException: continue if show_obj: diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py index 3f5cfc3..8e2bc0b 100755 --- a/sickbeard/providers/newznab.py +++ b/sickbeard/providers/newznab.py @@ -602,7 +602,7 @@ class NewznabProvider(generic.NZBProvider): if tvid_prodid: try: - show_obj = helpers.find_show_by_id(tvid_prodid, no_mapped_ids=False) + show_obj = helpers.find_show_by_id(tvid_prodid, no_mapped_ids=False, check_multishow=True) except MultipleShowObjectsException: return None return show_obj diff --git a/sickbeard/search_queue.py b/sickbeard/search_queue.py index 4fc0024..ef56201 100644 --- a/sickbeard/search_queue.py +++ b/sickbeard/search_queue.py @@ -369,16 +369,11 @@ class RecentSearchQueueItem(generic_queue.QueueItem): for cur_result in sql_result: tvid, prodid = int(cur_result['tvid']), int(cur_result['prodid']) - try: - if not show_obj or not (show_obj.tvid == tvid and show_obj.prodid == prodid): - show_obj = helpers.find_show_by_id({tvid: prodid}) - - # for when there is orphaned series in the database but not loaded into our showlist - if not show_obj: - continue + if not show_obj or not (show_obj.tvid == tvid and show_obj.prodid == prodid): + show_obj = helpers.find_show_by_id({tvid: prodid}) - except exceptions_helper.MultipleShowObjectsException: - logger.log(u'ERROR: expected to find a single show matching %s' % cur_result['showid']) + # for when there is orphaned series in the database but not loaded into our showlist + if not show_obj: continue try: diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py index acaf839..3466f9d 100644 --- a/sickbeard/show_queue.py +++ b/sickbeard/show_queue.py @@ -760,6 +760,7 @@ class QueueItemAdd(ShowQueueItem): # add it to the show list sickbeard.showList.append(self.show_obj) + sickbeard.showDict[self.show_obj.sid_int] = self.show_obj try: self.show_obj.load_episodes_from_tvinfo() diff --git a/sickbeard/tv.py b/sickbeard/tv.py index a8f4a28..8e9cc63 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -214,7 +214,7 @@ class TVShow(TVShowBase): self._prodid = int(prodid) self.sid_int = self.create_sid(self._tvid, self._prodid) self._paused = 0 - self._mapped_ids = {} # type: Dict + self.internal_ids = {} # type: Dict self._not_found_count = None # type: None or int self._last_found_on_indexer = -1 # type: int @@ -229,7 +229,7 @@ class TVShow(TVShowBase): # noinspection PyTypeChecker self.release_groups = None # type: BlackAndWhiteList - show_obj = helpers.find_show_by_id(self.sid_int) + show_obj = helpers.find_show_by_id(self.sid_int, check_multishow=True) if None is not show_obj: raise exceptions_helper.MultipleShowObjectsException('Can\'t create a show if it already exists') @@ -363,14 +363,14 @@ class TVShow(TVShowBase): @property def ids(self): - if not self._mapped_ids: + if not self.internal_ids: acquired_lock = self.lock.acquire(False) if acquired_lock: try: indexermapper.map_indexers_to_show(self) finally: self.lock.release() - return self._mapped_ids + return self.internal_ids @ids.setter def ids(self, value): @@ -383,7 +383,7 @@ class TVShow(TVShowBase): v.get('status') not in indexermapper.MapStatus.allstatus or \ not isinstance(v.get('date'), datetime.date): return - self._mapped_ids = value + self.internal_ids = value @property def is_anime(self): @@ -1514,6 +1514,10 @@ class TVShow(TVShowBase): # remove self from show list sickbeard.showList = filter_list(lambda so: so.tvid_prodid != self.tvid_prodid, sickbeard.showList) + try: + del sickbeard.showDict[self.sid_int] + except (BaseException, Exception): + pass # clear the cache ic = image_cache.ImageCache() @@ -1752,6 +1756,13 @@ class TVShow(TVShowBase): name_cache.buildNameCache(self) self.reset_not_found_count() + old_sid_int = self.create_sid(old_tvid, old_prodid) + if old_sid_int != self.sid_int: + try: + del sickbeard.showDict[old_sid_int] + except (BaseException, Exception): + pass + sickbeard.showDict[self.sid_int] = self # force the update try: diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py index dac316c..7a39421 100644 --- a/sickbeard/tvcache.py +++ b/sickbeard/tvcache.py @@ -286,7 +286,7 @@ class TVCache(object): show_obj = None if tvid_prodid: try: - show_obj = helpers.find_show_by_id(tvid_prodid, no_mapped_ids=False) + show_obj = helpers.find_show_by_id(tvid_prodid, no_mapped_ids=False, check_multishow=True) except MultipleShowObjectsException: return diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 7f3af8d..4766368 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -2279,7 +2279,7 @@ class Home(MainHandler): m_prodid = helpers.try_int(m_prodid) show_obj = helpers.find_show_by_id({tvid: prodid}, no_mapped_ids=True) try: - m_show_obj = helpers.find_show_by_id({m_tvid: m_prodid}, no_mapped_ids=False) + m_show_obj = helpers.find_show_by_id({m_tvid: m_prodid}, no_mapped_ids=False, check_multishow=True) except exceptions_helper.MultipleShowObjectsException: msg = 'Duplicate shows in DB' ui.notifications.message('TV info source switch', 'Error: ' + msg) @@ -2360,7 +2360,7 @@ class Home(MainHandler): not sickbeard.TVInfoAPI(m_tvid).config.get('mapped_only') and \ (m_tvid != show_obj.tvid or m_prodid != show_obj.prodid): try: - new_show_obj = helpers.find_show_by_id({m_tvid: m_prodid}, no_mapped_ids=False) + new_show_obj = helpers.find_show_by_id({m_tvid: m_prodid}, no_mapped_ids=False, check_multishow=True) mtvid_prodid = TVidProdid({m_tvid: m_prodid})() if not new_show_obj or (new_show_obj.tvid == show_obj.tvid and new_show_obj.prodid == show_obj.prodid): master_ids += [bool(helpers.try_int(kwargs.get(x))) for x in ('paused', 'markwanted')] diff --git a/sickgear.py b/sickgear.py index 78e5cd5..71c6d47 100755 --- a/sickgear.py +++ b/sickgear.py @@ -626,10 +626,12 @@ class SickGear(object): sql_result = my_db.select('SELECT indexer AS tv_id, indexer_id AS prod_id, location FROM tv_shows') sickbeard.showList = [] + sickbeard.showDict = {} for cur_result in sql_result: try: show_obj = TVShow(int(cur_result['tv_id']), int(cur_result['prod_id'])) sickbeard.showList.append(show_obj) + sickbeard.showDict[show_obj.sid_int] = show_obj except (BaseException, Exception) as err: logger.log('There was an error creating the show in %s: %s' % ( cur_result['location'], ex(err)), logger.ERROR) diff --git a/tests/tv_tests.py b/tests/tv_tests.py index 5333b9f..40d03b1 100644 --- a/tests/tv_tests.py +++ b/tests/tv_tests.py @@ -22,8 +22,20 @@ import unittest import test_lib as test from random import randint +import datetime +import copy import sickbeard from sickbeard.tv import TVEpisode, TVShow, TVidProdid, prodid_bitshift +from exceptions_helper import ex, MultipleShowObjectsException +from sickbeard.helpers import find_show_by_id +from sickbeard import indexermapper +from sickbeard.indexers.indexer_api import TVInfoAPI +from sickbeard.indexers.indexer_config import TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TVDB, TVINFO_TVMAZE, \ + TVINFO_TVRAGE + +# noinspection PyUnreachableCode +if False: + from typing import Optional class TVShowTests(test.SickbeardTestDBCase): @@ -170,6 +182,88 @@ class TVidProdidTests(test.SickbeardTestDBCase): self.assertEqual(prodid, str_reverse_obj.prodid, msg='reverse str prodid test%s' % msg_vars) +ids_base = {source: {'id': 0, 'status': indexermapper.MapStatus.NO_AUTOMATIC_CHANGE, 'date': datetime.date.today()} + for source in TVInfoAPI().all_sources} + +shows = [{'tvid': TVINFO_TVDB, 'prodid': 123, + 'ids': {TVINFO_TVMAZE: {'id': 22}, TVINFO_IMDB: {'id': 54321}, TVINFO_TMDB: {'id': 9877}}}, + {'tvid': TVINFO_TVDB, 'prodid': 222, + 'ids': {TVINFO_TVMAZE: {'id': 854}, TVINFO_IMDB: {'id': 9435}, TVINFO_TMDB: {'id': 2457}}}, + {'tvid': TVINFO_TVMAZE, 'prodid': 123, + 'ids': {TVINFO_TVMAZE: {'id': 957}, TVINFO_IMDB: {'id': 4751}, TVINFO_TMDB: {'id': 659}}}, + {'tvid': TVINFO_TMDB, 'prodid': 123, + 'ids': {TVINFO_TVMAZE: {'id': 428}, TVINFO_IMDB: {'id': 999}, TVINFO_TMDB: {'id': 754}}} + ] + +find_tests = [ + {'para': {'show_id': {TVINFO_TVMAZE: 22, TVINFO_TVRAGE: 785}, 'no_mapped_ids': False}, + 'result': {'tvid': TVINFO_TVDB, 'prodid': 123}, + 'description': 'search via mapped id'}, + {'para': {'show_id': {TVINFO_TVDB: 123}}, 'result': {'tvid': TVINFO_TVDB, 'prodid': 123}, + 'description': 'simple standard search via master id dict'}, + {'para': {'show_id': {TVINFO_TVDB: 12345}}, 'result': None, + 'description': 'simple standard search via master id dict, for non-existing show'}, + {'para': {'show_id': {TVINFO_TVDB: 123, TVINFO_TVMAZE: 123}, 'check_multishow': True}, + 'result': {'success': False}, + 'description': 'search via 2 ids matching multiple shows and multi show check'}, + {'para': {'show_id': {TVINFO_TVDB: 5555, TVINFO_TVMAZE: 123}, 'check_multishow': True}, + 'result': {'tvid': TVINFO_TVMAZE, 'prodid': 123}, + 'description': 'search via 2 ids matching only 1 show and multi show check'}, + {'para': {'show_id': {TVINFO_TVDB: 123, TVINFO_TVMAZE: 123}}, + 'result': {'tvid': TVINFO_TVDB, 'prodid': 123}, + 'description': 'search via 2 ids matching only 1 show without multi show check #1'}, + {'para': {'show_id': {TVINFO_TVDB: 123, TVINFO_TVRAGE: 22}}, + 'result': {'tvid': TVINFO_TVDB, 'prodid': 123}, + 'description': 'search via 2 ids matching only 1 show without multi show check #2'}, + {'para': {'show_id': {TVINFO_TVMAZE: 22, TVINFO_TVRAGE: 785}}, + 'result': None, + 'description': 'search for 2 non-existing ids without mapping'}, + {'para': {'show_id': {TVINFO_TMDB: 123}}, + 'result': None, 'description': 'invalid sid search (tvid above tvid_bitmask)'}, + {'para': {'show_id': '%s:123' % TVINFO_TVDB}, 'result': {'tvid': TVINFO_TVDB, 'prodid': 123}, + 'description': 'simple search via tvid_prodid string'}, + {'para': {'show_id': '%s:123' % TVINFO_TVDB, 'check_multishow': True}, + 'result': {'tvid': TVINFO_TVDB, 'prodid': 123}, + 'description': 'simple search via tvid_prodid string and check multishow'}, + ] + + +class TVFindTests(test.SickbeardTestDBCase): + def setUp(self): + super(TVFindTests, self).setUp() + sickbeard.showList = [] + sickbeard.showDict = {} + sickbeard.indexermapper.indexer_list = [i for i in TVInfoAPI().all_sources] + for show in shows: + sh = TVShow(show['tvid'], show['prodid']) + ids = copy.deepcopy(ids_base) + if show.get('ids'): + for sub_ids in show['ids']: + ids[sub_ids].update(show['ids'][sub_ids]) + ids[show['tvid']]['status'] = indexermapper.MapStatus.SOURCE + ids[show['tvid']]['id'] = show['prodid'] + sh.ids = ids + sickbeard.showList.append(sh) + sickbeard.showDict[sh.sid_int] = sh + + def test_find_show_by_id(self): + result = None # type: Optional[TVShow] + for show_test in find_tests: + success = True + try: + result = find_show_by_id(**show_test['para']) + except MultipleShowObjectsException: + success = False + if isinstance(show_test['result'], dict) and None is not show_test['result'].get('success', None): + self.assertEqual(success, show_test['result'].get('success', None), + msg='error finding show (%s) with para: %s' % + (show_test.get('description'), show_test['para'])) + else: + self.assertEqual(result and {'tvid': result.tvid, 'prodid': result.prodid}, show_test['result'], + msg='error finding show (%s) with para: %s' % + (show_test.get('description'), show_test['para'])) + + if '__main__' == __name__: print('==================') print('STARTING - TV TESTS') @@ -189,3 +283,6 @@ if '__main__' == __name__: print('######################################################################') suite = unittest.TestLoader().loadTestsFromTestCase(TVidProdidTests) unittest.TextTestRunner(verbosity=2).run(suite) + print('######################################################################') + suite = unittest.TestLoader().loadTestsFromTestCase(TVFindTests) + unittest.TextTestRunner(verbosity=2).run(suite)