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)