diff --git a/CHANGES.md b/CHANGES.md index 22e50652..d92326dd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,99 @@ -### 0.14.0 (2018-02-01 02:30:00 UTC) +### 0.15.0 (2018-xx-xx xx:xx:xx UTC) + +* Change overhaul and add API functions +* Change API version... start with 10 +* Change set application response header to 'SickGear' + add API version +* Change return timezone (of network) in API +* Add indexer to calls +* Add SickGear Command tip for old SickBeard commands +* Add warning old sickbeard API calls only support tvdb shows +* Add "tvdbid" fallback only for sickbeard calls +* Add listcommands +* Add list of all commands (old + new) in listcommand page at the beginning +* Change hide 'listcommands' command from commands list, since it needs the API builder CSS + is html not json +* Add missing help in webapi +* Add episode info: absolute_number, scene_season, scene_episode, scene_absolute_number +* Add fork to SB command +* Add sg +* Add sg.activatescenenumbering +* Add sg.addrootdir +* Add sg.checkscheduler +* Add sg.deleterootdir +* Add sg.episode +* Add sg.episode.search +* Add sg.episode.setstatus +* Add sg.episode.subtitlesearch +* Add sg.exceptions +* Add sg.forcesearch +* Add sg.future +* Add sg.getdefaults +* Add sg.getindexericon +* Add sg.getindexers to list all indexers +* Add sg.getmessages +* Add sg.getnetworkicon +* Add sg.getrootdirs +* Add sg.getqualities +* Add sg.getqualitystrings +* Add sg.history +* Add sg.history.clear +* Add sg.history.trim +* Add sg.listtraktaccounts +* Add sg.listignorewords +* Add sg.listrequiedwords +* Add sg.logs +* Add sg.pausebacklog +* Add sg.postprocess +* Add sg.ping +* Add sg.restart +* Add sg.searchqueue +* Add sg.searchtv to search all indexers +* Add sg.setexceptions +* Add sg.setignorewords +* Add sg.setrequiredwords +* Add sg.setscenenumber +* Add sg.show +* Add sg.show.addexisting +* Add sg.show.addnew +* Add sg.show.cache +* Add sg.show.delete +* Add sg.show.getbanner +* Add sg.show.getfanart +* Add sg.show.getposter +* Add sg.show.getquality +* Add sg.show.listfanart +* Add sg.show.ratefanart +* Add sg.show.seasonlist +* Add sg.show.seasons +* Add sg.show.setquality +* Add sg.show.stats +* Add sg.show.refresh +* Add sg.show.pause +* Add sg.show.update +* Add sg.shows +* Add sg.shows.browsetrakt +* Add sg.shows.forceupdate +* Add sg.shows.queue +* Add sg.shows.stats +* Change sickbeard to sickgear +* Change sickbeard_call to property +* Change sg.episode.setstatus allow setting of quality +* Change sg.history, history command output +* Change sg.searchtv to list of indexers +* Add uhd4kweb to qualities +* Add upgrade_once to add existing shows +* Add upgrade_once to add new show +* Add upgrade_once to show quality settings (get/set) +* Add 'ids' to Show + Shows +* Add ids to coming eps + get tvdb id from ids +* Add 'status_str' to coming eps +* Add 'local_datetime' to comming eps + runtime +* Add X-Filename response header to getbanner, getposter +* Add X-Fanartname response header for sg.show.getfanart + +[develop changelog] + + +### 0.14.0 (2018-02-01 02:30:00 UTC) * Change improve core scheduler logic * Change improve media process to parse anime format 'Show Name 123 - 001 - Ep 1 name' diff --git a/gui/slick/interfaces/default/apiBuilder.tmpl b/gui/slick/interfaces/default/apiBuilder.tmpl index 44fb1233..0f3232c9 100644 --- a/gui/slick/interfaces/default/apiBuilder.tmpl +++ b/gui/slick/interfaces/default/apiBuilder.tmpl @@ -3,7 +3,9 @@ API Builder - - +
@@ -372,9 +596,10 @@ addOption("show.pause-opt", "Pause", "&pause=1"); +
- +
diff --git a/gui/slick/interfaces/default/config_general.tmpl b/gui/slick/interfaces/default/config_general.tmpl index 9425f486..6f2db9c2 100644 --- a/gui/slick/interfaces/default/config_general.tmpl +++ b/gui/slick/interfaces/default/config_general.tmpl @@ -481,7 +481,7 @@ API enable -

permit the use of the SickGear (SickBeard) API

+

permit the use of the SickGear (and Legacy SickBeard) API

@@ -490,6 +490,7 @@ ' % f + else: + table_sickgear_commands += '' % f + color = ("", " style='color: grey !important;'")[is_old_command] + out += '

%s%s

' % (color, f, ("", " (Sickbeard compatibility command)")[is_old_command]) + if isinstance(help, dict): + sg_c = '' + if "SickGearCommand" in help: + sg_c += '' % help['SickGearCommand'] + out += "

for all features use SickGear API Command: %s

" % help['SickGearCommand'] + if "desc" in help: + if is_old_command: + table_sickbeard_commands += '%s' % (help['desc'], sg_c) + else: + table_sickgear_commands += '' % help['desc'] + out += help['desc'] + + table = '' + + if "requiredParameters" in help and isinstance(help['requiredParameters'], dict): + for p, d in help['requiredParameters'].iteritems(): + des = '' + if isinstance(d, dict) and 'desc' in d: + des = d.get('desc') + table += "" % (p, des) + + if "optionalParameters" in help and isinstance(help['optionalParameters'], dict): + for p, d in help['optionalParameters'].iteritems(): + des = '' + if isinstance(d, dict) and 'desc' in d: + des = d.get('desc') + table += "" % (p, des) + if table: + out += "
%s
%s%s%s%s
%s required

%s

%s optional

%s

" + out += table + out += '
ParameterDescription
' + else: + if is_old_command: + table_sickbeard_commands += '%s' % 'no description' + else: + table_sickgear_commands += '%s' % 'no description' + + if is_old_command: + table_sickbeard_commands += '' + else: + table_sickgear_commands += '' + + if table_sickbeard_commands: + out = "

SickBeard Commands (compatibility):

" + table_sickbeard_commands + '
CommandDescriptionReplacement SickGear Command
' + out + + if table_sickgear_commands: + out = "

SickGear Commands:

" + table_sickgear_commands + '
CommandDescription
' + out + + return out + class CMD_Help(ApiCall): _help = {"desc": "display help information for a given subject/command", @@ -673,7 +802,7 @@ class CMD_Help(ApiCall): return out -class CMD_ComingEpisodes(ApiCall): +class CMD_SickGearComingEpisodes(ApiCall): _help = {"desc": "display the coming episodes", "optionalParameters": {"sort": {"desc": "change the sort order"}, "type": {"desc": "one or more of allowedValues separated by |"}, @@ -709,28 +838,51 @@ class CMD_ComingEpisodes(ApiCall): myDB = db.DBConnection() sql_results = myDB.select( - "SELECT airdate, airs, episode, name AS 'ep_name', description AS 'ep_plot', network, season, showid AS 'indexerid', show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE season != 0 AND airdate >= ? AND airdate <= ? AND tv_shows.indexer_id = tv_episodes.showid AND tv_episodes.status NOT IN (" + ','.join( - ['?'] * len(qualList)) + ")", [yesterday, next_week] + qualList) + "SELECT airdate, airs, runtime, tv_shows.indexer AS 'indexer', episode, name AS 'ep_name', " + "tv_episodes.status as 'status', description AS 'ep_plot', network, season, showid AS 'indexerid', " + "show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', " + "tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE " + + ("", "tv_shows.indexer = %s AND " % INDEXER_TVDB)[self.sickbeard_call] + + "season != 0 AND airdate >= ? AND " + "airdate <= ? AND tv_shows.indexer_id = tv_episodes.showid AND tv_shows.indexer == tv_episodes.indexer AND " + "tv_episodes.status NOT IN (" + ','.join(['?'] * len(qualList)) + ")", [yesterday, next_week] + qualList) for cur_result in sql_results: - done_show_list.append(int(cur_result["indexerid"])) + done_show_list.append((int(cur_result["indexerid"]), int(cur_result["indexer"]))) - more_sql_results = myDB.select( - "SELECT airdate, airs, episode, name AS 'ep_name', description AS 'ep_plot', network, season, showid AS 'indexerid', show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', tv_shows.paused AS 'paused' FROM tv_episodes outer_eps, tv_shows WHERE season != 0 AND showid NOT IN (" + ','.join( - ['?'] * len( - done_show_list)) + ") AND tv_shows.indexer_id = outer_eps.showid AND airdate = (SELECT airdate FROM tv_episodes inner_eps WHERE inner_eps.season != 0 AND inner_eps.showid = outer_eps.showid AND inner_eps.airdate >= ? ORDER BY inner_eps.airdate ASC LIMIT 1) AND outer_eps.status NOT IN (" + ','.join( - ['?'] * len(Quality.DOWNLOADED + Quality.SNATCHED)) + ")", - done_show_list + [next_week] + Quality.DOWNLOADED + Quality.SNATCHED) + more_sql_results = [m for m in myDB.select( + "SELECT airdate, airs, runtime, tv_shows.indexer AS 'indexer', episode, name AS 'ep_name', " + "outer_eps.status as 'status', description AS 'ep_plot', network, season, showid AS 'indexerid', " + "show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', " + "tv_shows.paused AS 'paused' FROM tv_episodes outer_eps, tv_shows WHERE " + + ("", "tv_shows.indexer = %s AND " % INDEXER_TVDB)[self.sickbeard_call] + + "season != 0 AND " + "tv_shows.indexer_id = outer_eps.showid AND tv_shows.indexer == outer_eps.indexer AND " + "airdate = (SELECT airdate FROM tv_episodes inner_eps WHERE inner_eps.season != 0 AND " + "inner_eps.showid = outer_eps.showid AND inner_eps.indexer == outer_eps.indexer AND " + "inner_eps.airdate >= ? ORDER BY inner_eps.airdate ASC LIMIT 1) AND " + "outer_eps.status NOT IN (" + ','.join(['?'] * len(Quality.DOWNLOADED + Quality.SNATCHED)) + ")", + [next_week] + Quality.DOWNLOADED + Quality.SNATCHED) if (int(m['indexerid']), int(m['indexer'])) + not in done_show_list] sql_results += more_sql_results more_sql_results = myDB.select( - "SELECT airdate, airs, episode, name AS 'ep_name', description AS 'ep_plot', network, season, showid AS 'indexerid', show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE season != 0 AND tv_shows.indexer_id = tv_episodes.showid AND airdate <= ? AND airdate >= ? AND tv_episodes.status = ? AND tv_episodes.status NOT IN (" + ','.join( + "SELECT airdate, airs, runtime, tv_shows.indexer AS 'indexer', episode, name AS 'ep_name', " + "tv_episodes.status as 'status', description AS 'ep_plot', network, season, showid AS 'indexerid', " + "show_name, tv_shows.quality AS quality, tv_shows.status AS 'show_status', " + "tv_shows.paused AS 'paused' FROM tv_episodes, tv_shows WHERE " + + ("", "tv_shows.indexer = %s AND " % INDEXER_TVDB)[self.sickbeard_call] + + "season != 0 AND tv_shows.indexer_id = tv_episodes.showid AND tv_shows.indexer == tv_episodes.indexer AND " + "airdate <= ? AND airdate >= ? AND " + "tv_episodes.status = ? AND tv_episodes.status NOT IN (" + ','.join( ['?'] * len(qualList)) + ")", [tomorrow, recently, WANTED] + qualList) sql_results += more_sql_results sql_results = list(set(sql_results)) # make a dict out of the sql results - sql_results = [dict(row) for row in sql_results] + sql_results = [dict(row) for row in sql_results + if Quality.splitCompositeStatus(helpers.tryInt(row['status']))[0] not in + [DOWNLOADED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, ARCHIVED, IGNORED, SKIPPED]] # multi dimension sort sorts = { @@ -752,9 +904,15 @@ class CMD_ComingEpisodes(ApiCall): # add parsed_datetime to the dict for index, item in enumerate(sql_results): - sql_results[index]['parsed_datetime'] = network_timezones.parse_date_time(item['airdate'], item['airs'], item['network']) + timezone, sql_results[index]['timezone'] = network_timezones.get_network_timezone(item['network'], + return_name=True) + p_t = network_timezones.parse_date_time(item['airdate'], item['airs'], timezone) + sql_results[index]['parsed_datetime'] = p_t + sql_results[index]['local_datetime'] = sbdatetime.sbdatetime.sbstrftime( + sbdatetime.sbdatetime.convert_to_setting(p_t, force_local=True), dateTimeFormat) sql_results[index]['data_show_name'] = value_maybe_article(item['show_name']) sql_results[index]['data_network'] = value_maybe_article(item['network']) + sql_results[index]['status_str'] = statusStrings[item['status']] sql_results.sort(sorts[self.sort]) @@ -801,7 +959,14 @@ class CMD_ComingEpisodes(ApiCall): # start day of the week on 1 (monday) ep['weekday'] = 1 + datetime.date.fromordinal(ep['airdate']).weekday() # Add tvdbid for backward compability - ep["tvdbid"] = ep['indexerid'] + try: + showObj = helpers.find_show_by_id(sickbeard.showList, {ep['indexer']: ep['indexerid']}) + ep['tvdbid'] = showObj.ids.get(INDEXER_TVDB, {'id': 0})['id'] + ep['ids'] = {k: v.get('id') for k, v in showObj.ids.iteritems()} + except (StandardError, Exception): + ep['tvdbid'] = (None, ep['indexerid'])[INDEXER_TVDB == ep['indexer']] + ep['ids'] = None + ep['airdate'] = sbdatetime.sbdatetime.sbfdate(datetime.date.fromordinal(ep['airdate']), d_preset=dateFormat) ep['parsed_datetime'] = sbdatetime.sbdatetime.sbfdatetime(ep['parsed_datetime'], d_preset=dateFormat, t_preset='%H:%M %z') @@ -814,19 +979,41 @@ class CMD_ComingEpisodes(ApiCall): return _responds(RESULT_SUCCESS, finalEpResults) -class CMD_Episode(ApiCall): - _help = {"desc": "display detailed info about an episode", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - "season": {"desc": "the season number"}, - "episode": {"desc": "the episode number"} - }, - "optionalParameters": {"full_path": { - "desc": "show the full absolute path (if valid) instead of a relative path for the episode location"} - } - } +class CMD_ComingEpisodes(CMD_SickGearComingEpisodes): + _help = {"desc": "display the coming episodes", + "optionalParameters": {"sort": {"desc": "change the sort order"}, + "type": {"desc": "one or more of allowedValues separated by |"}, + "paused": { + "desc": "0 to exclude paused shows, 1 to include them, or omitted to use the SB default"}, + }, + "SickGearCommand": "sg.future", + } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearComingEpisodes.__init__(self, handler, args, kwargs) + + +class CMD_SickGearEpisode(ApiCall): + _help = {"desc": "display detailed info about an episode", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + "season": {"desc": "the season number"}, + "episode": {"desc": "the episode number"} + }, + "optionalParameters": {"full_path": { + "desc": "show the full absolute path (if valid) instead of a relative path for the episode location"}, + + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) self.s, args = self.check_params(args, kwargs, "season", None, True, "int", []) self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", []) @@ -837,14 +1024,16 @@ class CMD_Episode(ApiCall): def run(self): """ display detailed info about an episode """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") myDB = db.DBConnection(row_type="dict") sqlResults = myDB.select( - "SELECT name, description, airdate, status, location, file_size, release_name, subtitles FROM tv_episodes WHERE showid = ? AND episode = ? AND season = ?", - [self.indexerid, self.e, self.s]) + "SELECT name, description, airdate, status, location, file_size, release_name, subtitles, absolute_number, " + "scene_season, scene_episode, scene_absolute_number FROM tv_episodes WHERE indexer = ? AND showid = ? " + "AND episode = ? AND season = ?", + [self.indexer, self.indexerid, self.e, self.s]) if not len(sqlResults) == 1: raise ApiError("Episode not found") episode = sqlResults[0] @@ -865,7 +1054,8 @@ class CMD_Episode(ApiCall): elif not showPath: # show dir is broken ... episode path will be empty episode["location"] = "" # convert stuff to human form - episode['airdate'] = sbdatetime.sbdatetime.sbfdate(sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(int(episode['airdate']), showObj.airs, showObj.network)), d_preset=dateFormat) + timezone, episode['timezone'] = network_timezones.get_network_timezone(showObj.network, return_name=True) + episode['airdate'] = sbdatetime.sbdatetime.sbfdate(sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(int(episode['airdate']), showObj.airs, timezone)), d_preset=dateFormat) status, quality = Quality.splitCompositeStatus(int(episode["status"])) episode["status"] = _get_status_Strings(status) episode["quality"] = _get_quality_string(quality) @@ -874,16 +1064,39 @@ class CMD_Episode(ApiCall): return _responds(RESULT_SUCCESS, episode) -class CMD_EpisodeSearch(ApiCall): - _help = {"desc": "search for an episode. the response might take some time", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, +class CMD_Episode(CMD_SickGearEpisode): + _help = {"desc": "display detailed info about an episode", + "requiredParameters": {"indexerid": {"desc": "unique id of a show"}, "season": {"desc": "the season number"}, "episode": {"desc": "the episode number"} - } + }, + "optionalParameters": {"full_path": { + "desc": "show the full absolute path (if valid) instead of a relative path for the episode location"}, + }, + "SickGearCommand": "sg.episode", + } + + def __init__(self, handler, args, kwargs): + # required + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearEpisode.__init__(self, handler, args, kwargs) + + +class CMD_SickGearEpisodeSearch(ApiCall): + _help = {"desc": "search for an episode. the response might take some time", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + "season": {"desc": "the season number"}, + "episode": {"desc": "the episode number"} + }, } def __init__(self, handler, args, kwargs): # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) self.s, args = self.check_params(args, kwargs, "season", None, True, "int", []) self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", []) @@ -893,7 +1106,7 @@ class CMD_EpisodeSearch(ApiCall): def run(self): """ search for an episode """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -920,32 +1133,55 @@ class CMD_EpisodeSearch(ApiCall): return _responds(RESULT_FAILURE, msg='Unable to find episode') -class CMD_EpisodeSetStatus(ApiCall): - _help = {"desc": "set status of an episode or season (when no ep is provided)", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, +class CMD_EpisodeSearch(CMD_SickGearEpisodeSearch): + _help = {"desc": "search for an episode. the response might take some time", + "requiredParameters": {"tvdbid": {"desc": "thetvdb.com id of a show"}, "season": {"desc": "the season number"}, - "status": {"desc": "the status values: wanted, skipped, archived, ignored, failed"} - }, - "optionalParameters": {"episode": {"desc": "the episode number"}, - "force": {"desc": "should we replace existing (downloaded) episodes or not"} - } - } + "episode": {"desc": "the episode number"} + }, + "SickGearCommand": "episode.search", + } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearEpisodeSearch.__init__(self, handler, args, kwargs) + + +class CMD_SickGearEpisodeSetStatus(ApiCall): + _help = {"desc": "set status of an episode or season (when no ep is provided)", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + "season": {"desc": "the season number"}, + "status": {"desc": "the status values: wanted, skipped, archived, ignored, failed, snatched, downloaded"} + }, + "optionalParameters": {"episode": {"desc": "the episode number"}, + "force": {"desc": "should we replace existing (downloaded) episodes or not"}, + "quality": {"desc": "set quality of episode(s), only for statuses: snatched, downloaded, archived"}, + } + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) self.s, args = self.check_params(args, kwargs, "season", None, True, "int", []) self.status, args = self.check_params(args, kwargs, "status", None, True, "string", - ["wanted", "skipped", "archived", "ignored", "failed"]) + ["wanted", "skipped", "archived", "ignored", "failed", "snatched", "downloaded"]) # optional self.e, args = self.check_params(args, kwargs, "episode", None, False, "int", []) self.force, args = self.check_params(args, kwargs, "force", 0, False, "bool", []) + self.quality, args = self.check_params(args, kwargs, "quality", None, False, "string", [q for q in quality_map]) # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): """ set status of an episode or a season (when no ep is provided) """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -958,6 +1194,12 @@ class CMD_EpisodeSetStatus(ApiCall): # the allowed values has at least one item that could not be matched against the internal status strings raise ApiError("The status string could not be matched to a status. Report to Devs!") + if None is not self.quality: + if self.status not in (SNATCHED, SNATCHED_BEST, SNATCHED_PROPER, DOWNLOADED, ARCHIVED): + return _responds(RESULT_FAILURE, msg="Can't set status %s together with quailty: %s" % + (statusStrings[self.status], self.quality)) + self.quality = quality_map[self.quality] + ep_list = [] if self.e: epObj = showObj.getEpisode(self.s, self.e) @@ -996,13 +1238,16 @@ class CMD_EpisodeSetStatus(ApiCall): continue # allow the user to force setting the status for an already downloaded episode - if epObj.status in Quality.DOWNLOADED and not self.force: + if epObj.status in Quality.DOWNLOADED and not self.force and None is self.quality: ep_results.append(_epResult(RESULT_FAILURE, epObj, "Refusing to change status because it is already marked as DOWNLOADED")) failure = True continue - epObj.status = self.status + if None is not self.quality: + epObj.status = Quality.compositeStatus(self.status, self.quality) + else: + epObj.status = self.status result = epObj.get_sql() if None is not result: sql_l.append(result) @@ -1032,16 +1277,40 @@ class CMD_EpisodeSetStatus(ApiCall): return _responds(RESULT_SUCCESS, msg='All status set successfully.' + extra_msg) -class CMD_SubtitleSearch(ApiCall): - _help = {"desc": "search episode subtitles. the response might take some time", +class CMD_EpisodeSetStatus(CMD_SickGearEpisodeSetStatus): + _help = {"desc": "set status of an episode or season (when no ep is provided)", "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, + "season": {"desc": "the season number"}, + "status": {"desc": "the status values: wanted, skipped, archived, ignored, failed"} + }, + "optionalParameters": {"episode": {"desc": "the episode number"}, + "force": {"desc": "should we replace existing (downloaded) episodes or not"} + }, + "SickGearCommand": "sg.episode.setstatus", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + kwargs['indexer'] = INDEXER_TVDB + CMD_SickGearEpisodeSetStatus.__init__(self, handler, args, kwargs) + + +class CMD_SickGearSubtitleSearch(ApiCall): + _help = {"desc": "search episode subtitles. the response might take some time", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, "season": {"desc": "the season number"}, "episode": {"desc": "the episode number"} - } + }, } def __init__(self, handler, args, kwargs): # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) self.s, args = self.check_params(args, kwargs, "season", None, True, "int", []) self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", []) @@ -1051,7 +1320,7 @@ class CMD_SubtitleSearch(ApiCall): def run(self): """ search episode subtitles """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -1084,16 +1353,37 @@ class CMD_SubtitleSearch(ApiCall): return response -class CMD_Exceptions(ApiCall): +class CMD_SubtitleSearch(ApiCall): + _help = {"desc": "search episode subtitles. the response might take some time", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, + "season": {"desc": "the season number"}, + "episode": {"desc": "the episode number"} + }, + "SickGearCommand": "sg.episode.subtitlesearch", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + ApiCall.__init__(self, handler, args, kwargs) + + +class CMD_SickGearExceptions(ApiCall): _help = {"desc": "display scene exceptions for all or a given show", - "optionalParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - } - } + "optionalParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + } + } def __init__(self, handler, args, kwargs): # required # optional self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int", + [i for i in indexer_api.indexerApi().indexers]) # super, missing, help ApiCall.__init__(self, handler, args, kwargs) @@ -1111,7 +1401,7 @@ class CMD_Exceptions(ApiCall): scene_exceptions[indexerid].append(row["show_name"]) else: - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -1125,12 +1415,95 @@ class CMD_Exceptions(ApiCall): return _responds(RESULT_SUCCESS, scene_exceptions) -class CMD_History(ApiCall): - _help = {"desc": "display sickbeard downloaded/snatched history", +class CMD_Exceptions(CMD_SickGearExceptions): + _help = {"desc": "display scene exceptions for all or a given show", + "optionalParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.exceptions", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearExceptions.__init__(self, handler, args, kwargs) + + +class CMD_SetExceptions(ApiCall): + _help = {"desc": "set scene exceptions for a given show", + "requiredParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + "forseason": {"desc": "exception for season, -1 for all seasons"}, + }, + "optionalParameters": {"add": {"desc": "list of exceptions to add"}, + "remove": {"desc": "list of exceptions to remove"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) + self.forseason, args = self.check_params(args, kwargs, "forseason", None, True, "int", []) + # optional + self.add, args = self.check_params(args, kwargs, "add", None, False, "list", []) + self.remove, args = self.check_params(args, kwargs, "remove", None, False, "list", []) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + if not self.add and not self.remove: + return _responds(RESULT_FAILURE, 'No Exceptions provided to be add or removed.') + + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) + if not showObj: + return _responds(RESULT_FAILURE, 'Could not find any show in db from indexer: %s with id: %s' % + (self.indexer, self.indexerid)) + + myDB = db.DBConnection(row_type="dict") + sqlResults = myDB.select("SELECT show_name, season, indexer_id AS 'indexerid' FROM scene_exceptions WHERE " + "indexer_id = ? and season = ?", [self.indexerid, self.forseason]) + + cl = [] + curexep = [(s['show_name'], s['season']) for s in sqlResults] + add_list = [] + remove_list = [] + if self.remove: + for r in self.remove: + if (r, self.forseason) in curexep: + cl.append(['DELETE FROM scene_exceptions WHERE indexer_id = ? AND season = ? AND show_name = ?', + [self.indexerid, self.forseason, r]]) + try: + curexep.remove((r, self.forseason)) + except ValueError: + pass + remove_list.append(r) + + if self.add: + for a in self.add: + if (a, self.forseason) not in curexep: + cl.append(['INSERT INTO scene_exceptions (show_name, indexer_id, season) VALUES (?,?,?)', + [a, self.indexerid, self.forseason]]) + curexep.append((a, self.forseason)) + add_list.append(a) + + if cl: + myDB.mass_action(cl) + return _responds(RESULT_SUCCESS, data={'added': add_list, 'removed': remove_list, 'for season': self.forseason, + 'current': [c[0] for c in curexep], 'indexer': self.indexer, + 'indexerid': self.indexerid}, + msg='Exceptions changed.') + + +class CMD_SickGearHistory(ApiCall): + _help = {"desc": "display sickgear downloaded/snatched history", "optionalParameters": {"limit": {"desc": "limit returned results"}, "type": {"desc": "only show a specific type of results"}, - } - } + } + } def __init__(self, handler, args, kwargs): # required @@ -1141,7 +1514,7 @@ class CMD_History(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ display sickbeard downloaded/snatched history """ + """ display sickgear downloaded/snatched history """ typeCodes = [] if self.type == "downloaded": @@ -1158,12 +1531,15 @@ class CMD_History(ApiCall): ulimit = min(int(self.limit), 100) if ulimit == 0: sqlResults = myDB.select( - "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id AND action in (" + ','.join( - ['?'] * len(typeCodes)) + ") ORDER BY date DESC", typeCodes) + "SELECT h.*, show_name, s.indexer FROM history h, tv_shows s WHERE h.showid=s.indexer_id" + + ("", " AND s.indexer=%s" % INDEXER_TVDB)[self.sickbeard_call] + + " AND action in (" + ','.join(['?'] * len(typeCodes)) + ") ORDER BY date DESC", typeCodes) else: sqlResults = myDB.select( - "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id AND action in (" + ','.join( - ['?'] * len(typeCodes)) + ") ORDER BY date DESC LIMIT ?", typeCodes + [ulimit]) + "SELECT h.*, show_name, s.indexer FROM history h, tv_shows s WHERE h.showid=s.indexer_id" + + ("", " AND s.indexer=%s" % INDEXER_TVDB)[self.sickbeard_call] + + " AND action in (" + ','.join(['?'] * len(typeCodes)) + ") ORDER BY date DESC LIMIT ?", + typeCodes + [ulimit]) results = [] for row in sqlResults: @@ -1179,15 +1555,31 @@ class CMD_History(ApiCall): row["resource_path"] = os.path.dirname(row["resource"]) row["resource"] = os.path.basename(row["resource"]) # Add tvdbid for backward compability - row['tvdbid'] = row['indexerid'] + row['tvdbid'] = (None, row['indexerid'])[INDEXER_TVDB == row['indexer']] results.append(row) return _responds(RESULT_SUCCESS, results) -class CMD_HistoryClear(ApiCall): - _help = {"desc": "clear sickbeard's history", - } +class CMD_History(CMD_SickGearHistory): + _help = {"desc": "display sickgear downloaded/snatched history", + "optionalParameters": {"limit": {"desc": "limit returned results"}, + "type": {"desc": "only show a specific type of results"}, + }, + "SickGearCommand": "sg.history", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearHistory.__init__(self, handler, args, kwargs) + + +class CMD_SickGearHistoryClear(ApiCall): + _help = {"desc": "clear sickgear's history", + } def __init__(self, handler, args, kwargs): # required @@ -1196,16 +1588,29 @@ class CMD_HistoryClear(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ clear sickbeard's history """ + """ clear sickgear's history """ myDB = db.DBConnection() myDB.action("DELETE FROM history WHERE 1=1") return _responds(RESULT_SUCCESS, msg="History cleared") -class CMD_HistoryTrim(ApiCall): - _help = {"desc": "trim sickbeard's history by removing entries greater than 30 days old" - } +class CMD_HistoryClear(CMD_SickGearHistoryClear): + _help = {"desc": "clear sickgear's history", + "SickGearCommand": "sg.history.clear", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearHistoryClear.__init__(self, handler, args, kwargs) + + +class CMD_SickGearHistoryTrim(ApiCall): + _help = {"desc": "trim sickgear's history by removing entries greater than 30 days old" + } def __init__(self, handler, args, kwargs): # required @@ -1214,7 +1619,7 @@ class CMD_HistoryTrim(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ trim sickbeard's history """ + """ trim sickgear's history """ myDB = db.DBConnection() myDB.action("DELETE FROM history WHERE date < " + str( (datetime.datetime.today() - datetime.timedelta(days=30)).strftime(history.dateFormat))) @@ -1222,11 +1627,24 @@ class CMD_HistoryTrim(ApiCall): return _responds(RESULT_SUCCESS, msg="Removed history entries greater than 30 days old") -class CMD_Logs(ApiCall): - _help = {"desc": "view sickbeard's log", +class CMD_HistoryTrim(CMD_SickGearHistoryTrim): + _help = {"desc": "trim sickgear's history by removing entries greater than 30 days old", + "SickGearCommand": "sg.history.trim", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearHistoryTrim.__init__(self, handler, args, kwargs) + + +class CMD_SickGearLogs(ApiCall): + _help = {"desc": "view sickgear's log", "optionalParameters": {"min_level ": { "desc": "the minimum level classification of log entries to show, with each level inherting its above level"}} - } + } def __init__(self, handler, args, kwargs): # required @@ -1297,7 +1715,22 @@ class CMD_Logs(ApiCall): return _responds(RESULT_SUCCESS, final_data) -class CMD_PostProcess(ApiCall): +class CMD_Logs(CMD_SickGearLogs): + _help = {"desc": "view sickgear's log", + "optionalParameters": {"min_level ": { + "desc": "the minimum level classification of log entries to show, with each level inherting its above level"}}, + "SickGearCommand": "sg.logs", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearLogs.__init__(self, handler, args, kwargs) + + +class CMD_SickGearPostProcess(ApiCall): _help = {"desc": "Manual postprocess TV Download Dir", "optionalParameters": {"path": {"desc": "Post process this folder"}, "force_replace": {"desc": "Force already Post Processed Dir/Files"}, @@ -1339,8 +1772,28 @@ class CMD_PostProcess(ApiCall): return _responds(RESULT_SUCCESS, data=data, msg="Started postprocess for %s" % self.path) -class CMD_SickBeard(ApiCall): - _help = {"desc": "display misc sickbeard related information"} +class CMD_PostProcess(CMD_SickGearPostProcess): + _help = {"desc": "Manual postprocess TV Download Dir", + "optionalParameters": {"path": {"desc": "Post process this folder"}, + "force_replace": {"desc": "Force already Post Processed Dir/Files"}, + "return_data": {"desc": "Returns result for the process"}, + "process_method": {"desc": "Symlink, hardlink, move or copy the file"}, + "is_priority": {"desc": "Replace the file even if it exists in a higher quality)"}, + "type": {"desc": "What type of postprocess request is this, auto of manual"} + }, + "SickGearCommand": "sg.postprocess", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearPostProcess.__init__(self, handler, args, kwargs) + + +class CMD_SickGear(ApiCall): + _help = {"desc": "display misc sickgear related information"} def __init__(self, handler, args, kwargs): # required @@ -1349,19 +1802,31 @@ class CMD_SickBeard(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ display misc sickbeard related information """ - data = {"sb_version": sickbeard.BRANCH, "api_version": Api.version, - "api_commands": sorted(_functionMaper.keys())} + """ display misc sickgear related information """ + data = {"sb_version": sickbeard.BRANCH, "api_version": Api.version, "fork": "SickGear", + "api_commands": sorted(x for x in _functionMaper.keys() if 'listcommands' != x)} return _responds(RESULT_SUCCESS, data) -class CMD_SickBeardAddRootDir(ApiCall): - _help = {"desc": "add a sickbeard user's parent directory", +class CMD_SickBeard(CMD_SickGear): + _help = {"desc": "display misc sickgear related information", + "SickGearCommand": "sg",} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGear.__init__(self, handler, args, kwargs) + + +class CMD_SickGearAddRootDir(ApiCall): + _help = {"desc": "add a sickgear user's parent directory", "requiredParameters": {"location": {"desc": "the full path to root (parent) directory"} - }, + }, "optionalParameters": {"default": {"desc": "make the location passed the default root (parent) directory"} - } - } + } + } def __init__(self, handler, args, kwargs): # required @@ -1372,7 +1837,7 @@ class CMD_SickBeardAddRootDir(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ add a parent directory to sickbeard's config """ + """ add a parent directory to sickgear's config """ self.location = urllib.unquote_plus(self.location) location_matched = 0 @@ -1413,7 +1878,24 @@ class CMD_SickBeardAddRootDir(ApiCall): return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directories updated") -class CMD_SickBeardCheckScheduler(ApiCall): +class CMD_SickBeardAddRootDir(CMD_SickGearAddRootDir): + _help = {"desc": "add a sickgear user's parent directory", + "requiredParameters": {"location": {"desc": "the full path to root (parent) directory"} + }, + "optionalParameters": {"default": {"desc": "make the location passed the default root (parent) directory"} + }, + "SickGearCommand": "sg.addrootdir", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearAddRootDir.__init__(self, handler, args, kwargs) + + +class CMD_SickGearCheckScheduler(ApiCall): _help = {"desc": "query the scheduler"} def __init__(self, handler, args, kwargs): @@ -1437,10 +1919,22 @@ class CMD_SickBeardCheckScheduler(ApiCall): return _responds(RESULT_SUCCESS, data) -class CMD_SickBeardDeleteRootDir(ApiCall): - _help = {"desc": "delete a sickbeard user's parent directory", +class CMD_SickBeardCheckScheduler(CMD_SickGearCheckScheduler): + _help = {"desc": "query the scheduler", + "SickGearCommand": "sg.checkscheduler"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearCheckScheduler.__init__(self, handler, args, kwargs) + + +class CMD_SickGearDeleteRootDir(ApiCall): + _help = {"desc": "delete a sickgear user's parent directory", "requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}} - } + } def __init__(self, handler, args, kwargs): # required @@ -1450,7 +1944,7 @@ class CMD_SickBeardDeleteRootDir(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ delete a parent directory from sickbeard's config """ + """ delete a parent directory from sickgear's config """ if sickbeard.ROOT_DIRS == "": return _responds(RESULT_FAILURE, _getRootDirs(), msg="No root directories detected") @@ -1483,26 +1977,63 @@ class CMD_SickBeardDeleteRootDir(ApiCall): return _responds(RESULT_SUCCESS, _getRootDirs(), msg="Root directory deleted") -class CMD_SickBeardForceSearch(ApiCall): - _help = {'desc': 'force the episode recent search early'} +class CMD_SickBeardDeleteRootDir(CMD_SickGearDeleteRootDir): + _help = {"desc": "delete a sickgear user's parent directory", + "requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}}, + "SickGearCommand": "sg.deleterootdir" + } def __init__(self, handler, args, kwargs): # required # optional # super, missing, help + self.sickbeard_call = True + CMD_SickGearDeleteRootDir.__init__(self, handler, args, kwargs) + + +class CMD_SickGearForceSearch(ApiCall): + _help = {'desc': 'force the given search type searches', + "requiredParameters": {"searchtype": {"desc": "type of search to be forced: recent, backlog, proper"}} + } + + def __init__(self, handler, args, kwargs): + # required + self.searchtype, args = self.check_params(args, kwargs, "searchtype", "recent", True, "string", + ["recent", "backlog", "proper"]) + # optional + # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ force the episode search early """ - # Searching all providers for any needed episodes - result = sickbeard.recentSearchScheduler.forceRun() + """ force the given search type search """ + result = None + if 'recent' == self.searchtype: + result = sickbeard.recentSearchScheduler.forceRun() + elif 'backlog' == self.searchtype: + result = sickbeard.backlogSearchScheduler.force_search(force_type=FORCED_BACKLOG) + elif 'proper' == self.searchtype: + result = sickbeard.properFinderScheduler.forceRun() if result: - return _responds(RESULT_SUCCESS, msg='Episode recent search successfully forced') - return _responds(RESULT_FAILURE, msg='Can not force the episode recent search because it\'s already active') + return _responds(RESULT_SUCCESS, msg='%s search successfully forced' % self.searchtype) + return _responds(RESULT_FAILURE, + msg='Can not force the %s search because it\'s already active' % self.searchtype) -class CMD_SickBeardGetDefaults(ApiCall): - _help = {"desc": "get sickbeard user defaults"} +class CMD_SickBeardForceSearch(CMD_SickGearForceSearch): + _help = {'desc': 'force the episode recent search early', + "SickGearCommand": "sg.forcesearch",} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + kwargs['searchtype'] = 'recent' + self.sickbeard_call = True + CMD_SickGearForceSearch.__init__(self, handler, args, kwargs) + + +class CMD_SickGearSearchQueue(ApiCall): + _help = {'desc': 'list sickgear\'s search queue'} def __init__(self, handler, args, kwargs): # required @@ -1511,17 +2042,43 @@ class CMD_SickBeardGetDefaults(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ get sickbeard user defaults """ + """ list sickgear's search queue """ + return _responds(RESULT_SUCCESS, sickbeard.searchQueueScheduler.action.queue_length()) + + +class CMD_SickGearGetDefaults(ApiCall): + _help = {"desc": "get sickgear user defaults"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ get sickgear user defaults """ anyQualities, bestQualities = _mapQuality(sickbeard.QUALITY_DEFAULT) data = {"status": statusStrings[sickbeard.STATUS_DEFAULT].lower(), "flatten_folders": int(sickbeard.FLATTEN_FOLDERS_DEFAULT), "initial": anyQualities, - "archive": bestQualities, "future_show_paused": int(sickbeard.EPISODE_VIEW_DISPLAY_PAUSED)} + "archive": bestQualities, "future_show_paused": int(sickgear.EPISODE_VIEW_DISPLAY_PAUSED)} return _responds(RESULT_SUCCESS, data) -class CMD_SickBeardGetMessages(ApiCall): +class CMD_SickBeardGetDefaults(CMD_SickGearGetDefaults): + _help = {"desc": "get sickgear user defaults", + "SickGearCommand": "sg.getdefaults"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearGetDefaults.__init__(self, handler, args, kwargs) + + +class CMD_SickGearGetMessages(ApiCall): _help = {"desc": "get all messages"} def __init__(self, handler, args, kwargs): @@ -1539,8 +2096,20 @@ class CMD_SickBeardGetMessages(ApiCall): return _responds(RESULT_SUCCESS, messages) -class CMD_SickBeardGetRootDirs(ApiCall): - _help = {"desc": "get sickbeard user parent directories"} +class CMD_SickBeardGetMessages(CMD_SickGearGetMessages): + _help = {"desc": "get all messages", + "SickGearCommand": "sg.getmessages"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearGetMessages.__init__(self, handler, args, kwargs) + + +class CMD_SickGearGetQualities(ApiCall): + _help = {"desc": "get all qualities"} def __init__(self, handler, args, kwargs): # required @@ -1549,15 +2118,135 @@ class CMD_SickBeardGetRootDirs(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ get the parent directories defined in sickbeard's config """ + return _responds(RESULT_SUCCESS, quality_map) + + +class CMD_SickGearGetIndexers(ApiCall): + _help = {"desc": "get indexer list", + "optionalParameters": {"searchable-only ": {"desc": "searchable indexers only"}}} + + def __init__(self, handler, args, kwargs): + # required + # optional + self.searchable_only, args = self.check_params(args, kwargs, "searchable-only", False, False, "bool", []) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + result = {} + for i in indexer_config.indexerConfig: + for d, v in indexer_config.indexerConfig[i].iteritems(): + if self.searchable_only and (indexer_config.indexerConfig[i].get('mapped_only') or + not indexer_config.indexerConfig[i].get('active') or + indexer_config.indexerConfig[i].get('defunct')): + continue + if d in ['id', 'name', 'show_url', 'mapped_only', 'main_url'] and \ + isinstance(v, (basestring, tuple, dict, list, int, long, float, bool)): + if 'mapped_only' == d: + key = 'searchable' + val = not v and indexer_config.indexerConfig[i].get('active') and \ + not indexer_config.indexerConfig[i].get('defunct') + else: + key = d + val = (v, '%s{INDEXER-ID}' % v)['show_url' == d] + result.setdefault(i, {}).update({key: val}) + return _responds(RESULT_SUCCESS, result) + + +class CMD_SickGearGetIndexerIcon(ApiCall): + _help = {"desc": "get indexer icon", + "requiredParameters": {"indexer": {"desc": "indexer"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().all_indexers]) + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + # doesn't work + i = indexer_config.indexerConfig.get(self.indexer) + if not i: + self.handler.set_status(404) + return _responds(RESULT_FAILURE, 'Icon not found') + img = i['icon'] + image = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', img) + if not ek.ek(os.path.isfile, image): + self.handler.set_status(404) + return _responds(RESULT_FAILURE, 'Icon not found') + return {'outputType': 'image', 'image': self.handler.getImage(image)} + + +class CMD_SickGearGetNetworkIcon(ApiCall): + _help = {"desc": "get network icon", + "requiredParameters": {"network": {"desc": "name of network"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.network, args = self.check_params(args, kwargs, "network", None, True, "string", []) + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + image = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'network', + '%s.png' % self.network.lower()) + if not ek.ek(os.path.isfile, image): + self.handler.set_status(404) + return _responds(RESULT_FAILURE, 'Icon not found') + return {'outputType': 'image', 'image': self.handler.getImage(image)} + + +class CMD_SickGearGetqualityStrings(ApiCall): + _help = {"desc": "get human readable quality strings"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + return _responds(RESULT_SUCCESS, Quality.qualityStrings) + + +class CMD_SickGearGetRootDirs(ApiCall): + _help = {"desc": "get sickgear user parent directories"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ get the parent directories defined in sickgear's config """ return _responds(RESULT_SUCCESS, _getRootDirs()) -class CMD_SickBeardPauseBacklog(ApiCall): +class CMD_SickBeardGetRootDirs(CMD_SickGearGetRootDirs): + _help = {"desc": "get sickgear user parent directories", + "SickGearCommand": "sg.getrootdirs"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearGetRootDirs.__init__(self, handler, args, kwargs) + + +class CMD_SickGearPauseBacklog(ApiCall): _help = {"desc": "pause the backlog search", "optionalParameters": {"pause ": {"desc": "pause or unpause the global backlog"}} - } + } def __init__(self, handler, args, kwargs): # required @@ -1576,8 +2265,22 @@ class CMD_SickBeardPauseBacklog(ApiCall): return _responds(RESULT_SUCCESS, msg="Backlog unpaused") -class CMD_SickBeardPing(ApiCall): - _help = {"desc": "check to see if sickbeard is running"} +class CMD_SickBeardPauseBacklog(CMD_SickGearPauseBacklog): + _help = {"desc": "pause the backlog search", + "optionalParameters": {"pause ": {"desc": "pause or unpause the global backlog"}}, + "SickGearCommand": "sg.pausebacklog" + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearPauseBacklog.__init__(self, handler, args, kwargs) + + +class CMD_SickGearPing(ApiCall): + _help = {"desc": "check to see if sickgear is running",} def __init__(self, handler, args, kwargs): # required @@ -1586,7 +2289,7 @@ class CMD_SickBeardPing(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ check to see if sickbeard is running """ + """ check to see if sickgear is running """ self.handler.set_header('Cache-Control', "max-age=0,no-cache,no-store") if sickbeard.started: return _responds(RESULT_SUCCESS, {"pid": sickbeard.PID}, "Pong") @@ -1594,8 +2297,20 @@ class CMD_SickBeardPing(ApiCall): return _responds(RESULT_SUCCESS, msg="Pong") -class CMD_SickBeardRestart(ApiCall): - _help = {"desc": "restart sickbeard"} +class CMD_SickBeardPing(CMD_SickGearPing): + _help = {"desc": "check to see if sickgear is running", + "SickGearCommand": "sg.ping"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearPing.__init__(self, handler, args, kwargs) + + +class CMD_SickGearRestart(ApiCall): + _help = {"desc": "restart sickgear"} def __init__(self, handler, args, kwargs): # required @@ -1604,16 +2319,29 @@ class CMD_SickBeardRestart(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ restart sickbeard """ + """ restart sickgear """ sickbeard.events.put(sickbeard.events.SystemEvent.RESTART) return _responds(RESULT_SUCCESS, msg="SickGear is restarting...") -class CMD_SickBeardSearchIndexers(ApiCall): +class CMD_SickBeardRestart(CMD_SickGearRestart): + _help = {"desc": "restart sickgear", + "SickGearCommand": "sg.restart"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearRestart.__init__(self, handler, args, kwargs) + + +class CMD_SickGearSearchIndexers(ApiCall): _help = {"desc": "search for show on the indexers with a given string and language", "optionalParameters": {"name": {"desc": "name of the show you want to search for"}, "indexerid": {"desc": "thetvdb.com or tvrage.com unique id of a show"}, - "lang": {"desc": "the 2 letter abbreviation lang id"} + "lang": {"desc": "the 2 letter abbreviation lang id"}, + "indexer": {"desc": "indexer to search, use -1 to search all indexers"} } } @@ -1628,54 +2356,74 @@ class CMD_SickBeardSearchIndexers(ApiCall): # optional self.name, args = self.check_params(args, kwargs, "name", None, False, "string", []) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", []) - self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys()) - self.indexer, args = self.check_params(args, kwargs, "indexer", 1, False, "int", []) + # self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys()) + self.indexers, args = self.check_params(args, kwargs, "indexers", -1, False, "list", + [-1] + [i for i in indexer_api.indexerApi().search_indexers], int) # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ search for show at tvdb with a given string and language """ + """ search for show at indexers with a given string and language """ + if 1 > len(self.indexers) and -1 in self.indexers: + raise ApiError('Mix of -1 (all Indexer) and specific Indexer not allowed') + + all_indexer = 1 == len(self.indexers) and -1 == self.indexers[0] + if self.name and not self.indexerid: # only name was given - lINDEXER_API_PARMS = sickbeard.indexerApi(self.indexer).api_params.copy() - lINDEXER_API_PARMS['language'] = self.lang - lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsListUI - t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS) - - apiData = None - - try: - apiData = t[str(self.name).encode()] - except Exception as e: - pass - - if not apiData: - return _responds(RESULT_FAILURE, msg="Did not get result from tvdb") - results = [] - for curSeries in apiData: - results.append({"indexerid": int(curSeries['id']), - "tvdbid": int(curSeries['id']), - "name": curSeries['seriesname'], - "first_aired": curSeries['firstaired'], - "indexer": self.indexer}) + indexertosearch = (self.indexers, [i for i in indexer_api.indexerApi().indexers if + indexer_api.indexerApi(i).config.get('active') and + not indexer_api.indexerApi(i).config.get('mapped_only') and + not indexer_api.indexerApi(i).config.get('defunct')])[all_indexer] + for i in indexertosearch: + lINDEXER_API_PARMS = sickbeard.indexerApi(i).api_params.copy() + lINDEXER_API_PARMS['language'] = 'en' + lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsNoFilterListUI + t = sickbeard.indexerApi(i).indexer(**lINDEXER_API_PARMS) - lang_id = self.valid_languages[self.lang] - return _responds(RESULT_SUCCESS, {"results": results, "langid": lang_id}) + apiData = None - elif self.indexerid: - lINDEXER_API_PARMS = sickbeard.indexerApi(self.indexer).api_params.copy() + try: + apiData = t[str(self.name).encode(), False] + except (StandardError, Exception): + pass - lang_id = self.valid_languages[self.lang] - if self.lang and not self.lang == 'en': - lINDEXER_API_PARMS['language'] = self.lang + for curSeries in apiData: + s = {"indexerid": int(curSeries['id']), + "name": curSeries['seriesname'], + "first_aired": curSeries['firstaired'], + "indexer": i, + "aliases": curSeries.get('aliases', None), + "relevance": NewHomeAddShows.get_UWRatio(self.name, curSeries['seriesname'], + curSeries.get('aliases', None))} + if INDEXER_TVDB == i: + s["tvdbid"] = int(curSeries['id']) + else: + s["tvdbid"] = None + results.append(s) + + if not results: + return _responds(RESULT_FAILURE, msg="Did not get result from %s" % + ', '.join([sickbeard.indexerApi(i).name for i in indexertosearch])) + + results = sorted(results, key=lambda x: x['relevance'], reverse=True) + + return _responds(RESULT_SUCCESS, {"results": results, "langid": 'en'}) + + elif self.indexerid and not all_indexer and 1 == len(self.indexers): + lINDEXER_API_PARMS = sickbeard.indexerApi(self.indexers[0]).api_params.copy() + + lang_id = 'en' + lINDEXER_API_PARMS['language'] = 'en' + lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsNoFilterListUI lINDEXER_API_PARMS['actors'] = False - t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS) + t = sickbeard.indexerApi(self.indexers[0]).indexer(**lINDEXER_API_PARMS) try: - myShow = t[int(self.indexerid)] + myShow = t[int(self.indexerid), False] except (sickbeard.indexer_shownotfound, sickbeard.indexer_error): logger.log(u"API :: Unable to find show with id " + str(self.indexerid), logger.WARNING) return _responds(RESULT_SUCCESS, {"results": [], "langid": lang_id}) @@ -1687,17 +2435,42 @@ class CMD_SickBeardSearchIndexers(ApiCall): return _responds(RESULT_FAILURE, msg="Show contains no name, invalid result") showOut = [{"indexerid": self.indexerid, - "tvdbid": self.indexerid, + "indexer": self.indexers[0], "name": unicode(myShow.data['seriesname']), - "first_aired": myShow.data['firstaired']}] + "first_aired": myShow.data['firstaired'], + "aliases": myShow.data.get('aliases', None), + "relevance": NewHomeAddShows.get_UWRatio(self.name, myShow.data['seriesname'], + myShow.data.get('aliases', None))}] + if INDEXER_TVDB == self.indexers[0]: + showOut[0]["tvdbid"] = int(myShow.data['id']) + else: + showOut[0]["tvdbid"] = None + + showOut = sorted(showOut, key=lambda x: x['relevance'], reverse=True) return _responds(RESULT_SUCCESS, {"results": showOut, "langid": lang_id}) else: - return _responds(RESULT_FAILURE, msg="Either indexerid or name is required") + return _responds(RESULT_FAILURE, msg="Either indexer + indexerid or name is required") + + +class CMD_SickBeardSearchIndexers(CMD_SickGearSearchIndexers): + _help = {"desc": "search for show on the tvdb with a given string and language", + "optionalParameters": {"name": {"desc": "name of the show you want to search for"}, + "indexerid": {"desc": "thetvdb.com unique id of a show"}, + "lang": {"desc": "the 2 letter abbreviation lang id"}, + }, + "SickGearCommand": "sg.searchtv", + } + + def __init__(self, handler, args, kwargs): + kwargs['indexers'] = INDEXER_TVDB + # super, missing, help + self.sickbeard_call = True + CMD_SickGearSearchIndexers.__init__(self, handler, args, kwargs) class CMD_SickBeardSetDefaults(ApiCall): - _help = {"desc": "set sickbeard user defaults", + _help = {"desc": "set sickgear user defaults", "optionalParameters": {"initial": {"desc": "initial quality for the show"}, "archive": {"desc": "archive quality for the show"}, "flatten_folders": {"desc": "flatten subfolders within the show directory"}, @@ -1708,12 +2481,8 @@ class CMD_SickBeardSetDefaults(ApiCall): def __init__(self, handler, args, kwargs): # required # optional - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", - ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", - "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", - ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", - "hdbluray", "fullhdbluray"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map]) self.future_show_paused, args = self.check_params(args, kwargs, "future_show_paused", None, False, "bool", []) self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", None, False, "bool", []) self.status, args = self.check_params(args, kwargs, "status", None, False, "string", @@ -1722,18 +2491,7 @@ class CMD_SickBeardSetDefaults(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ set sickbeard user defaults """ - - quality_map = {'sdtv': Quality.SDTV, - 'sddvd': Quality.SDDVD, - 'hdtv': Quality.HDTV, - 'rawhdtv': Quality.RAWHDTV, - 'fullhdtv': Quality.FULLHDTV, - 'hdwebdl': Quality.HDWEBDL, - 'fullhdwebdl': Quality.FULLHDWEBDL, - 'hdbluray': Quality.HDBLURAY, - 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN} + """ set sickgear user defaults """ iqualityID = [] aqualityID = [] @@ -1771,8 +2529,81 @@ class CMD_SickBeardSetDefaults(ApiCall): return _responds(RESULT_SUCCESS, msg="Saved defaults") +class CMD_SickGearSetSceneNumber(ApiCall): + _help = {"desc": "set Scene Numbers", + "requiredParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + }, + "optionalParameters": {"forSeason": {"desc": "season number of a show"}, + "forEpisode": {"desc": "episode number of a show"}, + "forAbsolute": {"desc": "absolute episode number of a show"}, + "sceneSeason": {"desc": "scene season number of a show to set"}, + "sceneEpisode": {"desc": "scene episode number of a show to set"}, + "sceneAbsolute": {"desc": "scene absolute episode number of a show to set"}, + } + } + + def __init__(self, handler, args, kwargs): + # required + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) + # optional + self.forSeason, args = self.check_params(args, kwargs, "forSeason", None, False, "int", []) + self.forEpisode, args = self.check_params(args, kwargs, "forEpisode", None, False, "int", []) + self.forAbsolute, args = self.check_params(args, kwargs, "forAbsolute", None, False, "int", []) + self.sceneSeason, args = self.check_params(args, kwargs, "sceneSeason", None, False, "int", []) + self.sceneEpisode, args = self.check_params(args, kwargs, "sceneEpisode", None, False, "int", []) + self.sceneAbsolute, args = self.check_params(args, kwargs, "sceneAbsolute", None, False, "int", []) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ saving scene numbers """ + + result = set_scene_numbering_helper(self.indexerid, self.indexer, self.forSeason, self.forEpisode, + self.forAbsolute, self.sceneSeason, self.sceneEpisode, self.sceneEpisode) + + if not result['success']: + return _responds(RESULT_FAILURE, result) + + return _responds(RESULT_SUCCESS, result) + + +class CMD_SickGearActivateSceneNumber(ApiCall): + _help = {"desc": "De-/Activate Scene Numbers", + "requiredParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + "activate": {"desc": "de-/activate scene numbering"}}, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) + self.activate, args = self.check_params(args, kwargs, "activate", None, True, "bool", []) + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ de-/activate scene numbers """ + + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) + if not showObj: + return _responds(RESULT_FAILURE, msg="Can't find show") + + showObj.scene = int(self.activate) + showObj.saveToDB() + + return _responds(RESULT_SUCCESS, data={'indexer': self.indexer, 'indexerid': self.indexerid, + 'show_name': showObj.name, 'scenenumbering': showObj.is_scene}, + msg="Scene Numbering %sactivated" % ('de', '')[self.activate]) + + class CMD_SickBeardShutdown(ApiCall): - _help = {"desc": "shutdown sickbeard"} + _help = {"desc": "shutdown sickgear"} def __init__(self, handler, args, kwargs): # required @@ -1781,27 +2612,273 @@ class CMD_SickBeardShutdown(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ shutdown sickbeard """ + """ shutdown sickgear """ sickbeard.events.put(sickbeard.events.SystemEvent.SHUTDOWN) return _responds(RESULT_SUCCESS, msg="SickGear is shutting down...") -class CMD_Show(ApiCall): - _help = {"desc": "display information for a given show", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - } - } +class CMD_SickGearListIgnoreWords(ApiCall): + _help = {"desc": "list ignore words", + "optionalParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + } + } def __init__(self, handler, args, kwargs): # required + # optional + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int", + [i for i in indexer_api.indexerApi().indexers]) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ list ignore words """ + if self.indexer and self.indexerid: + myDB = db.DBConnection() + sqlResults = myDB.select('SELECT show_name, rls_ignore_words FROM tv_shows WHERE indexer = ? AND ' + 'indexer_id = ?', [self.indexer, self.indexerid]) + if sqlResults: + ignore_words = sqlResults[0]['rls_ignore_words'] + return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid, + 'show name': sqlResults[0]['show_name']} + return_type = '%s:' % sqlResults[0]['show_name'] + else: + return _responds(RESULT_FAILURE, msg='Show not found.') + elif (None is self.indexer) != (None is self.indexerid): + return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.') + else: + ignore_words = sickbeard.IGNORE_WORDS + return_data = {'type': 'global'} + return_type = 'Global' + + return_data['use regex'] = ignore_words.startswith('regex:') + return_data['ignore words'] = [w.strip() for w in ignore_words.replace('regex:', '').split(',')] + return _responds(RESULT_SUCCESS, data=return_data, msg="%s ignore words" % return_type) + + +class CMD_SickGearSetIgnoreWords(ApiCall): + _help = {"desc": "set ignore words", + "optionalParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + "add": {"desc": "add words to list"}, + "remove": {"desc": "remove words from list"}, + "regex": {"desc": "interpret ALL (including existing) ignore words as regex"}, + } + } + + def __init__(self, handler, args, kwargs): + # required + # optional + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int", + [i for i in indexer_api.indexerApi().indexers]) + self.add, args = self.check_params(args, kwargs, "add", None, False, "list", []) + self.remove, args = self.check_params(args, kwargs, "remove", None, False, "list", []) + self.regex, args = self.check_params(args, kwargs, "regex", None, False, "bool", []) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ set ignore words """ + if not self.add and not self.remove: + return _responds(RESULT_FAILURE, msg="No words to add/remove provided") + + def _create_ignore_words(): + use_regex = ignore_words.startswith('regex:') + ignore_list = [w.strip() for w in ignore_words.replace('regex:', '').split(',')] + + if None is not self.regex: + use_regex = self.regex + if self.add: + for a in self.add: + ignore_list.append(a) + if self.remove: + for r in self.remove: + try: + ignore_list.remove(r) + except ValueError: + pass + return use_regex, ignore_list, '%s%s' % (('', 'regex:')[use_regex], ', '.join(ignore_list)) + + if self.indexer and self.indexerid: + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) + if not showObj: + return _responds(RESULT_FAILURE, msg="Show not found") + + myDB = db.DBConnection() + sqlResults = myDB.select('SELECT show_name, rls_ignore_words FROM tv_shows WHERE indexer = ? AND ' + 'indexer_id = ?', [self.indexer, self.indexerid]) + + ignore_words = '' + if sqlResults: + ignore_words = sqlResults[0]['rls_ignore_words'] + + return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid, + 'show name': sqlResults[0]['show_name']} + return_type = '%s:' % sqlResults[0]['show_name'] + + use_regex, ignore_list, new_ignore_words = _create_ignore_words() + myDB.action('UPDATE tv_shows SET rls_ignore_words = ? WHERE indexer = ? AND indexer_id = ?', + [new_ignore_words, self.indexer, self.indexerid]) + elif (None is self.indexer) != (None is self.indexerid): + return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.') + else: + ignore_words = sickbeard.IGNORE_WORDS + use_regex, ignore_list, new_ignore_words = _create_ignore_words() + sickbeard.IGNORE_WORDS = new_ignore_words + sickbeard.save_config() + return_data = {'type': 'global'} + return_type = 'Global' + + return_data['use regex'] = use_regex + return_data['ignore words'] = ignore_list + return _responds(RESULT_SUCCESS, data=return_data, msg="%s set ignore words" % return_type) + + +class CMD_SickGearListRequireWords(ApiCall): + _help = {"desc": "list requried words", + "optionalParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + } + } + + def __init__(self, handler, args, kwargs): + # required + # optional + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int", + [i for i in indexer_api.indexerApi().indexers]) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ list required words """ + if self.indexer and self.indexerid: + myDB = db.DBConnection() + sqlResults = myDB.select('SELECT show_name, rls_require_words FROM tv_shows WHERE indexer = ? ' + 'AND indexer_id = ?', [self.indexer, self.indexerid]) + if sqlResults: + required_words = sqlResults[0]['rls_require_words'] + return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid, + 'show name': sqlResults[0]['show_name']} + return_type = '%s:' % sqlResults[0]['show_name'] + else: + return _responds(RESULT_FAILURE, msg='Show not found.') + elif (None is self.indexer) != (None is self.indexerid): + return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.') + else: + required_words = sickbeard.REQUIRE_WORDS + return_data = {'type': 'global'} + return_type = 'Global' + + return_data['use regex'] = required_words.startswith('regex:') + return_data['required words'] = [w.strip() for w in required_words.replace('regex:', '').split(',')] + return _responds(RESULT_SUCCESS, data=return_data, msg="%s required words" % return_type) + + +class CMD_SickGearSetRequrieWords(ApiCall): + _help = {"desc": "set required words", + "optionalParameters": {"indexerid": {"desc": "unique id of a show"}, + "indexer": {"desc": "indexer of a show"}, + "add": {"desc": "add words to list"}, + "remove": {"desc": "remove words from list"}, + "regex": {"desc": "interpret ALL (including existing) ignore words as regex"}, + } + } + + def __init__(self, handler, args, kwargs): + # required + # optional + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, False, "int", + [i for i in indexer_api.indexerApi().indexers]) + self.add, args = self.check_params(args, kwargs, "add", None, False, "list", []) + self.remove, args = self.check_params(args, kwargs, "remove", None, False, "list", []) + self.regex, args = self.check_params(args, kwargs, "regex", None, False, "bool", []) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ set ignore words """ + if not self.add and not self.remove: + return _responds(RESULT_FAILURE, msg="No words to add/remove provided") + + def _create_required_words(): + use_regex = requried_words.startswith('regex:') + require_list = [w.strip() for w in requried_words.replace('regex:', '').split(',')] + + if None is not self.regex: + use_regex = self.regex + if self.add: + for a in self.add: + require_list.append(a) + if self.remove: + for r in self.remove: + try: + require_list.remove(r) + except ValueError: + pass + return use_regex, require_list, '%s%s' % (('', 'regex:')[use_regex], ', '.join(require_list)) + + if self.indexer and self.indexerid: + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) + if not showObj: + return _responds(RESULT_FAILURE, msg="Show not found") + + myDB = db.DBConnection() + sqlResults = myDB.select('SELECT show_name, rls_require_words FROM tv_shows WHERE indexer = ? AND ' + 'indexer_id = ?', [self.indexer, self.indexerid]) + + requried_words = '' + if sqlResults: + requried_words = sqlResults[0]['rls_require_words'] + + return_data = {'type': 'show', 'indexer': self.indexer, 'indexerid': self.indexerid, + 'show name': sqlResults[0]['show_name']} + return_type = '%s:' % sqlResults[0]['show_name'] + + use_regex, required_list, new_required_words = _create_required_words() + myDB.action('UPDATE tv_shows SET rls_require_words = ? WHERE indexer = ? AND indexer_id = ?', + [new_required_words, self.indexer, self.indexerid]) + elif (None is self.indexer) != (None is self.indexerid): + return _responds(RESULT_FAILURE, msg='You must supply indexer + indexerid.') + else: + requried_words = sickbeard.REQUIRE_WORDS + use_regex, required_list, new_required_words = _create_required_words() + sickbeard.REQUIRE_WORDS = new_required_words + sickbeard.save_config() + return_data = {'type': 'global'} + return_type = 'Global' + + return_data['use regex'] = use_regex + return_data['required words'] = required_list + return _responds(RESULT_SUCCESS, data=return_data, msg="%s set requried words" % return_type) + + +class CMD_SickGearShow(ApiCall): + _help = {"desc": "display information for a given show", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + "optionalParameters": {"overview": {"desc": "include overview"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional + self.overview, args = self.check_params(args, kwargs, "overview", False, False, "bool", []) # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): """ display information for a given show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -1838,15 +2915,31 @@ class CMD_Show(ApiCall): showDict["airs"] = str(showObj.airs).replace('am', ' AM').replace('pm', ' PM').replace(' ', ' ') showDict["indexerid"] = self.indexerid showDict["tvrage_id"] = showObj.ids.get(INDEXER_TVRAGE, {'id': 0})['id'] + showDict['ids'] = {k: v.get('id') for k, v in showObj.ids.iteritems()} showDict["tvrage_name"] = showObj.name showDict["network"] = showObj.network if not showDict["network"]: showDict["network"] = "" showDict["status"] = showObj.status + showDict["scenenumbering"] = showObj.is_scene + showDict["archivefirstmatch"] = showObj.upgrade_once + showDict["irgnorewords"] = showObj.rls_ignore_words + showDict["requirewords"] = showObj.rls_require_words + if self.overview: + showDict["overview"] = showObj.overview + showDict["tag"] = showObj.tag + showDict["imdb_id"] = showObj.imdbid + showDict["classification"] = showObj.classification + showDict["runtime"] = showObj.runtime + showDict["startyear"] = showObj.startyear + showDict["indexer"] = showObj.indexer + timezone, showDict['timezone'] = network_timezones.get_network_timezone(showDict['network'], return_name=True) if showObj.nextaired: - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(showObj.nextaired, showDict['airs'], showDict['network'])) - showDict['airs'] = sbdatetime.sbdatetime.sbftime(dtEpisodeAirs, t_preset=timeFormat).lstrip('0').replace(' 0', ' ') + dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting( + network_timezones.parse_date_time(showObj.nextaired, showDict['airs'], timezone)) + showDict['airs'] = sbdatetime.sbdatetime.sbftime(dtEpisodeAirs, + t_preset=timeFormat).lstrip('0').replace(' 0', ' ') showDict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) else: showDict['next_ep_airdate'] = '' @@ -1854,39 +2947,49 @@ class CMD_Show(ApiCall): return _responds(RESULT_SUCCESS, showDict) -class CMD_ShowAddExisting(ApiCall): - _help = {"desc": "add a show in sickbeard with an existing folder", - "requiredParameters": {"tvdbid or tvrageid": {"desc": "thetvdb.com or tvrage.com id"}, +class CMD_Show(CMD_SickGearShow): + _help = {"desc": "display information for a given thetvdb.com show", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "optionalParameters": {"overview": {"desc": "include overview"}, + }, + "SickGearCommand": "sg.show", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShow.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowAddExisting(ApiCall): + _help = {"desc": "add a show in SickGear with an existing folder", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "indexer id of a show"}, "location": {"desc": "full path to the existing folder for the show"} }, "optionalParameters": {"initial": {"desc": "initial quality for the show"}, "archive": {"desc": "archive quality for the show"}, + "upgrade_once": {"desc": "upgrade only once"}, "flatten_folders": {"desc": "flatten subfolders for the show"}, "subtitles": {"desc": "allow search episode subtitle"} } } def __init__(self, handler, args, kwargs): - if "tvdbid" in args or "tvdbid" in kwargs: - _INDEXER_INT = 1 - _INDEXER = "tvdbid" - elif "tvrageid" in args or "tvrageid" in kwargs: - _INDEXER_INT = 2 - _INDEXER = "tvrageid" - else: - _INDEXER_INT = None - _INDEXER = None # required - self.indexerid, args = self.check_params(args, kwargs, _INDEXER, None, True, "int", []) - self.indexer = _INDEXER_INT + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().search_indexers]) self.location, args = self.check_params(args, kwargs, "location", None, True, "string", []) # optional - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", - ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", - "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", - ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", - "hdbluray", "fullhdbluray"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map]) + self.upgradeonce, args = self.check_params(args, kwargs, "upgrade_once", False, False, "bool", []) + self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", []) self.subtitles, args = self.check_params(args, kwargs, "subtitles", int(sickbeard.USE_SUBTITLES), False, "int", @@ -1895,8 +2998,8 @@ class CMD_ShowAddExisting(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ add a show in sickbeard with an existing folder """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ add a show in sickgear with an existing folder """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if showObj: return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in the database") @@ -1916,17 +3019,6 @@ class CMD_ShowAddExisting(ApiCall): if not indexerName: return _responds(RESULT_FAILURE, msg="Unable to retrieve information from indexer") - quality_map = {'sdtv': Quality.SDTV, - 'sddvd': Quality.SDDVD, - 'hdtv': Quality.HDTV, - 'rawhdtv': Quality.RAWHDTV, - 'fullhdtv': Quality.FULLHDTV, - 'hdwebdl': Quality.HDWEBDL, - 'fullhdwebdl': Quality.FULLHDWEBDL, - 'hdbluray': Quality.HDBLURAY, - 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN} - #use default quality as a failsafe newQuality = int(sickbeard.QUALITY_DEFAULT) iqualityID = [] @@ -1943,72 +3035,79 @@ class CMD_ShowAddExisting(ApiCall): newQuality = Quality.combineQualities(iqualityID, aqualityID) sickbeard.showQueueScheduler.action.addShow(int(self.indexer), int(self.indexerid), self.location, SKIPPED, - newQuality, int(self.flatten_folders)) + newQuality, int(self.flatten_folders), + upgrade_once=self.upgradeonce) return _responds(RESULT_SUCCESS, {"name": indexerName}, indexerName + " has been queued to be added") -class CMD_ShowAddNew(ApiCall): - _help = {"desc": "add a new show to sickbeard", - "requiredParameters": {"tvdbid or tvrageid": {"desc": "thetvdb.com or tvrage.com id"} - }, +class CMD_ShowAddExisting(CMD_SickGearShowAddExisting): + _help = {"desc": "add a show in sickgear with an existing folder", + "requiredParameters": {"tvdbid": {"desc": "thetvdb.com id"}, + "location": {"desc": "full path to the existing folder for the show"} + }, + "optionalParameters": {"initial": {"desc": "initial quality for the show"}, + "archive": {"desc": "archive quality for the show"}, + "flatten_folders": {"desc": "flatten subfolders for the show"}, + "subtitles": {"desc": "allow search episode subtitle"} + }, + "SickGearCommand": "sg.show.addexisting", + } + + def __init__(self, handler, args, kwargs): + kwargs['indexer'] = INDEXER_TVDB + # required + kwargs['indexerid'], args = self.check_params(args, kwargs, "tvdbid", None, True, "int", []) + # super, missing, help + self.sickbeard_call = True + CMD_SickGearShowAddExisting.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowAddNew(ApiCall): + _help = {"desc": "add a new show to sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of show"}, + "indexerid": {"desc": "id of show"}, + }, "optionalParameters": {"initial": {"desc": "initial quality for the show"}, "location": {"desc": "base path for where the show folder is to be created"}, "archive": {"desc": "archive quality for the show"}, + "upgrade_once": {"desc": "upgrade only once"}, "flatten_folders": {"desc": "flatten subfolders for the show"}, "status": {"desc": "status of missing episodes"}, - "lang": {"desc": "the 2 letter lang abbreviation id"}, "subtitles": {"desc": "allow search episode subtitle"}, "anime": {"desc": "set show to anime"}, "scene": {"desc": "show searches episodes by scene numbering"} - } - } - - valid_languages = { - 'el': 20, 'en': 7, 'zh': 27, 'it': 15, 'cs': 28, 'es': 16, 'ru': 22, - 'nl': 13, 'pt': 26, 'no': 9, 'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31, - 'de': 14, 'da': 10, 'fi': 11, 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32, - 'sv': 8, 'sl': 30} + } + } def __init__(self, handler, args, kwargs): - if "tvdbid" in args or "tvdbid" in kwargs: - _INDEXER_INT = 1 - _INDEXER = "tvdbid" - elif "tvrageid" in args or "tvrageid" in kwargs: - _INDEXER_INT = 2 - _INDEXER = "tvrageid" - else: - _INDEXER_INT = None - _INDEXER = None # required - self.indexerid, args = self.check_params(args, kwargs, _INDEXER, None, True, "int", []) - self.indexer = _INDEXER_INT + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().search_indexers]) # optional self.location, args = self.check_params(args, kwargs, "location", None, False, "string", []) - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", - ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", - "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", - ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", - "hdbluray", "fullhdbluray"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map]) + self.upgradeonce, args = self.check_params(args, kwargs, "upgrade_once", False, False, "bool", []) self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", []) self.status, args = self.check_params(args, kwargs, "status", None, False, "string", ["wanted", "skipped", "archived", "ignored"]) - self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys()) self.subtitles, args = self.check_params(args, kwargs, "subtitles", int(sickbeard.USE_SUBTITLES), False, "int", []) self.anime, args = self.check_params(args, kwargs, "anime", int(sickbeard.ANIME_DEFAULT), False, "int", []) self.scene, args = self.check_params(args, kwargs, "scene", int(sickbeard.SCENE_DEFAULT), False, "int", []) + self.lang = 'en' # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ add a show in sickbeard with an existing folder """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ add a show in sickgear with an existing folder """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if showObj: return _responds(RESULT_FAILURE, msg="An existing indexerid already exists in database") @@ -2024,17 +3123,6 @@ class CMD_ShowAddNew(ApiCall): if not ek.ek(os.path.isdir, self.location): return _responds(RESULT_FAILURE, msg="'" + self.location + "' is not a valid location") - quality_map = {'sdtv': Quality.SDTV, - 'sddvd': Quality.SDDVD, - 'hdtv': Quality.HDTV, - 'rawhdtv': Quality.RAWHDTV, - 'fullhdtv': Quality.FULLHDTV, - 'hdwebdl': Quality.HDWEBDL, - 'fullhdwebdl': Quality.FULLHDWEBDL, - 'hdbluray': Quality.HDBLURAY, - 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN} - # use default quality as a failsafe newQuality = int(sickbeard.QUALITY_DEFAULT) iqualityID = [] @@ -2097,27 +3185,63 @@ class CMD_ShowAddNew(ApiCall): sickbeard.showQueueScheduler.action.addShow(int(self.indexer), int(self.indexerid), showPath, newStatus, newQuality, int(self.flatten_folders), self.lang, self.subtitles, self.anime, - self.scene, new_show=True) # @UndefinedVariable + self.scene, new_show=True, upgrade_once=self.upgradeonce) # @UndefinedVariable return _responds(RESULT_SUCCESS, {"name": indexerName}, indexerName + " has been queued to be added") -class CMD_ShowCache(ApiCall): - _help = {"desc": "check sickbeard's cache to see if the banner or poster image for a show is valid", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"} +class CMD_ShowAddNew(CMD_SickGearShowAddNew): + _help = {"desc": "add a new show to sickgear", + "requiredParameters": {"tvdbid": {"desc": "thetvdb.com id"} + }, + "optionalParameters": {"initial": {"desc": "initial quality for the show"}, + "location": {"desc": "base path for where the show folder is to be created"}, + "archive": {"desc": "archive quality for the show"}, + "flatten_folders": {"desc": "flatten subfolders for the show"}, + "status": {"desc": "status of missing episodes"}, + "lang": {"desc": "the 2 letter lang abbreviation id"}, + "subtitles": {"desc": "allow search episode subtitle"}, + "anime": {"desc": "set show to anime"}, + "scene": {"desc": "show searches episodes by scene numbering"} + }, + "SickGearCommand": "sg.show.addnew", + } + + valid_languages = { + 'el': 20, 'en': 7, 'zh': 27, 'it': 15, 'cs': 28, 'es': 16, 'ru': 22, + 'nl': 13, 'pt': 26, 'no': 9, 'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31, + 'de': 14, 'da': 10, 'fi': 11, 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32, + 'sv': 8, 'sl': 30} + + def __init__(self, handler, args, kwargs): + kwargs['indexer'] = INDEXER_TVDB + kwargs['indexerid'], args = self.check_params(args, kwargs, "tvdbid", None, True, "int", []) + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearShowAddNew.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowCache(ApiCall): + _help = {"desc": "check sickgear's cache to see if the banner or poster image for a show is valid", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, } - } def __init__(self, handler, args, kwargs): # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ check sickbeard's cache to see if the banner or poster image for a show is valid """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ check sickgear's cache to see if the banner or poster image for a show is valid """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2137,22 +3261,41 @@ class CMD_ShowCache(ApiCall): return _responds(RESULT_SUCCESS, {"poster": has_poster, "banner": has_banner}) -class CMD_ShowDelete(ApiCall): - _help = {"desc": "delete a show in sickbeard", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, +class CMD_ShowCache(CMD_SickGearShowCache): + _help = {"desc": "check sickgear's cache to see if the banner or poster image for a show is valid", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.cache", } - } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowCache.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowDelete(ApiCall): + _help = {"desc": "delete a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ delete a show in sickbeard """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ delete a show in sickgear """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2164,76 +3307,285 @@ class CMD_ShowDelete(ApiCall): return _responds(RESULT_SUCCESS, msg=str(showObj.name) + " has been deleted") -class CMD_ShowGetQuality(ApiCall): - _help = {"desc": "get quality setting for a show in sickbeard", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"} +class CMD_ShowDelete(CMD_SickGearShowDelete): + _help = {"desc": "delete a show in sickgear", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.delete", } - } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowDelete.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowGetQuality(ApiCall): + _help = {"desc": "get quality setting for a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ get quality setting for a show in sickbeard """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ get quality setting for a show in sickgear """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") anyQualities, bestQualities = _mapQuality(showObj.quality) - return _responds(RESULT_SUCCESS, {"initial": anyQualities, "archive": bestQualities}) + data = {"initial": anyQualities, "archive": bestQualities} + + if not self.sickbeard_call: + data['upgrade_once'] = showObj.upgrade_once + + return _responds(RESULT_SUCCESS, data) -class CMD_ShowGetPoster(ApiCall): - _help = {"desc": "get the poster stored for a show in sickbeard", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"} +class CMD_ShowGetQuality(CMD_SickGearShowGetQuality): + _help = {"desc": "get quality setting for a show in sickgear", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.getquality", } - } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowGetQuality.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowGetPoster(ApiCall): + _help = {"desc": "get the poster stored for a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ get the poster for a show in sickbeard """ + """ get the poster for a show in sickgear """ return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'poster', True)} -class CMD_ShowGetBanner(ApiCall): - _help = {"desc": "get the banner stored for a show in sickbeard", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"} +class CMD_ShowGetPoster(CMD_SickGearShowGetPoster): + _help = {"desc": "get the poster stored for a show in sickgear", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.getposter", } - } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowGetPoster.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowGetBanner(ApiCall): + _help = {"desc": "get the banner stored for a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ get the banner for a show in sickbeard """ + """ get the banner for a show in sickgear """ return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'banner', True)} -class CMD_ShowPause(ApiCall): - _help = {"desc": "set a show's paused state in sickbeard", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - }, - "optionalParameters": {"pause": {"desc": "set the pause state of the show"} +class CMD_ShowGetBanner(CMD_SickGearShowGetBanner): + _help = {"desc": "get the banner stored for a show in sickgear", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.getbanner", } - } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowGetBanner.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowListFanart(ApiCall): + _help = {"desc": "list the fanart's stored for a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ list the fanart's for a show in sickgear """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) + if not showObj: + return _responds(RESULT_FAILURE, msg="Show not found") + + fanart = [] + rating_names = {10: 'group', 20: 'favorite', 30: 'avoid'} + cache_obj = image_cache.ImageCache() + for img in ek.ek(glob.glob, cache_obj.fanart_path(showObj.indexerid).replace('fanart.jpg', '*')) or []: + match = re.search(r'\.(\d+(?:\.(\w*?(\d*)))?\.(?:\w{5,8}))\.fanart\.', img, re.I) + if match and match.group(1): + fanart += [(match.group(1), rating_names.get(sickbeard.FANART_RATINGS.get(str(self.indexerid), {}).get(match.group(1), ''), ''))] + + return _responds(RESULT_SUCCESS, fanart) + + +class CMD_SickGearShowRateFanart(ApiCall): + _help = {"desc": "rate the fanart's stored for a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + "fanartname": {"desc": "fanart name form sg.show.listfanart"}, + "rating": {"desc": "rate: unrate, group, favorite, avoid"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + self.fanartname, args = self.check_params(args, kwargs, "fanartname", None, True, "string", []) + self.rating, args = self.check_params(args, kwargs, "rating", None, True, "string", + ['unrate', 'group', 'favorite', 'avoid']) + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ rate the fanart's for a show in sickgear """ + cache_obj = image_cache.ImageCache() + fanartfile = cache_obj.fanart_path(self.indexerid).replace('fanart.jpg', '%s.fanart.jpg' % self.fanartname) + if not ek.ek(os.path.isfile, fanartfile): + return _responds(RESULT_FAILURE, msg='Unknown Fanart') + fan_ratings = {'unrate': 0, 'group': 10, 'favorite': 20, 'avoid': 30} + if 'unrate' == self.rating and str(self.indexerid) in sickbeard.FANART_RATINGS \ + and self.fanartname in sickbeard.FANART_RATINGS[str(self.indexerid)]: + del sickbeard.FANART_RATINGS[str(self.indexerid)][self.fanartname] + else: + sickbeard.FANART_RATINGS[str(self.indexerid)][self.fanartname] = fan_ratings[self.rating] + sickbeard.save_config() + return _responds(RESULT_SUCCESS, msg='Rated Fanart: %s = %s' % (self.fanartname, self.rating)) + + +class CMD_SickGearShowGetFanart(ApiCall): + _help = {"desc": "get the fanart stored for a show in sickgear. X-Fanartname response header resturns Fanart name or default for not found", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + "optionalParameters": {"fanartname": {"desc": "fanart name form sg.show.listfanart"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) + self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) + # optional + self.fanartname, args = self.check_params(args, kwargs, "fanartname", None, False, "string", []) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ get the fanart for a show in sickgear """ + cache_obj = image_cache.ImageCache() + default_fanartfile = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'trans.png') + fanartfile = default_fanartfile + used_fanart = 'default' + if self.fanartname: + fanartfile = cache_obj.fanart_path(self.indexerid).replace('fanart.jpg', '%s.fanart.jpg' % self.fanartname) + if not ek.ek(os.path.isfile, fanartfile): + fanartfile = default_fanartfile + used_fanart = self.fanartname + else: + fanart = [] + for img in ek.ek(glob.glob, cache_obj.fanart_path(self.indexerid).replace('fanart.jpg', '*')) or []: + if not ek.ek(os.path.isfile, img): + continue + match = re.search(r'\.(\d+(?:\.(\w*?(\d*)))?\.(?:\w{5,8}))\.fanart\.', img, re.I) + if match and match.group(1): + fanart += [(img, match.group(1), sickbeard.FANART_RATINGS.get(str(self.indexerid), {}).get(match.group(1), 0))] + if fanart: + fanartsorted = sorted([f for f in fanart if f[2] != 30], key=lambda x: x[2], reverse=True) + max_fa = max([f[2] for f in fanartsorted]) + fanartsorted = [f for f in fanartsorted if f[2] == max_fa] + if fanartsorted: + random_fanart = randint(0, len(fanartsorted) - 1) + fanartfile = fanartsorted[random_fanart][0] + used_fanart = fanartsorted[random_fanart][1] + + if fanartfile and ek.ek(os.path.isfile, fanartfile): + with ek.ek(open, fanartfile, 'rb') as f: + mime_type, encoding = MimeTypes().guess_type(fanartfile) + self.handler.set_header('X-Fanartname', used_fanart) + self.handler.set_header('Content-Type', mime_type) + return {'outputType': 'image', 'image': f.read()} + + # we should never get here + return _responds(RESULT_FAILURE, msg='No Fanart found') + + +class CMD_SickGearShowPause(ApiCall): + _help = {"desc": "set a show's paused state in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + "optionalParameters": {"pause": {"desc": "set the pause state of the show"} + } + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional self.pause, args = self.check_params(args, kwargs, "pause", 0, False, "bool", []) @@ -2241,8 +3593,8 @@ class CMD_ShowPause(ApiCall): ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ set a show's paused state in sickbeard """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ set a show's paused state in sickgear """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2256,22 +3608,42 @@ class CMD_ShowPause(ApiCall): return _responds(RESULT_FAILURE, msg=str(showObj.name) + " was unable to be paused") -class CMD_ShowRefresh(ApiCall): - _help = {"desc": "refresh a show in sickbeard", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - } - } +class CMD_ShowPause(CMD_SickGearShowPause): + _help = {"desc": "set a show's paused state in sickgear", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "optionalParameters": {"pause": {"desc": "set the pause state of the show"} + }, + "SickGearCommand": "sg.show.pause", + } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowPause.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowRefresh(ApiCall): + _help = {"desc": "refresh a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"},}, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ refresh a show in sickbeard """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ refresh a show in sickgear """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2283,16 +3655,35 @@ class CMD_ShowRefresh(ApiCall): return _responds(RESULT_FAILURE, msg="Unable to refresh " + str(showObj.name)) -class CMD_ShowSeasonList(ApiCall): - _help = {"desc": "display the season list for a given show", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - }, - "optionalParameters": {"sort": {"desc": "change the sort order from descending to ascending"} - } - } +class CMD_ShowRefresh(CMD_SickGearShowRefresh): + _help = {"desc": "refresh a show in sickgear", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.refresh", + } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowRefresh.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowSeasonList(ApiCall): + _help = {"desc": "display the season list for a given show", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + "optionalParameters": {"sort": {"desc": "change the sort order from descending to ascending"} + } + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional self.sort, args = self.check_params(args, kwargs, "sort", "desc", False, "string", @@ -2302,17 +3693,19 @@ class CMD_ShowSeasonList(ApiCall): def run(self): """ display the season list for a given show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") myDB = db.DBConnection(row_type="dict") if self.sort == "asc": - sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season ASC", - [self.indexerid]) + sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? AND indexer = ? " + "ORDER BY season ASC", + [self.indexerid, self.indexer]) else: - sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC", - [self.indexerid]) + sqlResults = myDB.select("SELECT DISTINCT season FROM tv_episodes WHERE showid = ? AND indexer = ? " + "ORDER BY season DESC", + [self.indexerid, self.indexer]) seasonList = [] # a list with all season numbers for row in sqlResults: seasonList.append(int(row["season"])) @@ -2320,16 +3713,37 @@ class CMD_ShowSeasonList(ApiCall): return _responds(RESULT_SUCCESS, seasonList) -class CMD_ShowSeasons(ApiCall): - _help = {"desc": "display a listing of episodes for all or a given season", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - }, - "optionalParameters": {"season": {"desc": "the season number"}, - } - } +class CMD_ShowSeasonList(CMD_SickGearShowSeasonList): + _help = {"desc": "display the season list for a given show", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "optionalParameters": {"sort": {"desc": "change the sort order from descending to ascending"} + }, + "SickGearCommand": "sg.show.seasonlist", + } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowSeasonList.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowSeasons(ApiCall): + _help = {"desc": "display a listing of episodes for all or a given season", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + "optionalParameters": {"season": {"desc": "the season number"}, + } + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional self.season, args = self.check_params(args, kwargs, "season", None, False, "int", []) @@ -2338,22 +3752,30 @@ class CMD_ShowSeasons(ApiCall): def run(self): """ display a listing of episodes for all or a given show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") myDB = db.DBConnection(row_type="dict") if self.season == None: - sqlResults = myDB.select("SELECT name, episode, airdate, status, season FROM tv_episodes WHERE showid = ?", - [self.indexerid]) + sqlResults = myDB.select("SELECT name, description, absolute_number, scene_absolute_number, episode, " + "scene_episode, scene_season, airdate, status, season FROM tv_episodes " + "WHERE showid = ? AND indexer = ?", + [self.indexerid, self.indexer]) seasons = {} for row in sqlResults: status, quality = Quality.splitCompositeStatus(int(row["status"])) row["status"] = _get_status_Strings(status) row["quality"] = _get_quality_string(quality) - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(row['airdate'],showObj.airs,showObj.network)) + timezone, row['timezone'] = network_timezones.get_network_timezone(showObj.network, return_name=True) + dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time( + row['airdate'], showObj.airs, timezone)) row['airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) + row['scene_episode'] = helpers.tryInt(row['scene_episode']) + row['scene_season'] = helpers.tryInt(row['scene_season']) + row['absolute_number'] = helpers.tryInt(row['absolute_number']) + row['scene_absolute_number'] = helpers.tryInt(row['scene_absolute_number']) curSeason = int(row["season"]) curEpisode = int(row["episode"]) del row["season"] @@ -2364,8 +3786,9 @@ class CMD_ShowSeasons(ApiCall): else: sqlResults = myDB.select( - "SELECT name, episode, airdate, status FROM tv_episodes WHERE showid = ? AND season = ?", - [self.indexerid, self.season]) + "SELECT name, description, absolute_number, scene_absolute_number, episode, scene_episode, " + "scene_season, airdate, status FROM tv_episodes WHERE showid = ? AND indexer = ? AND season = ?", + [self.indexerid, self.indexer, self.season]) if len(sqlResults) is 0: return _responds(RESULT_FAILURE, msg="Season not found") seasons = {} @@ -2375,8 +3798,14 @@ class CMD_ShowSeasons(ApiCall): status, quality = Quality.splitCompositeStatus(int(row["status"])) row["status"] = _get_status_Strings(status) row["quality"] = _get_quality_string(quality) - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(row['airdate'], showObj.airs, showObj.network)) + timezone, row['timezone'] = network_timezones.get_network_timezone(showObj.network, return_name=True) + dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time( + row['airdate'], showObj.airs, timezone)) row['airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) + row['scene_episode'] = helpers.tryInt(row['scene_episode']) + row['scene_season'] = helpers.tryInt(row['scene_season']) + row['absolute_number'] = helpers.tryInt(row['absolute_number']) + row['scene_absolute_number'] = helpers.tryInt(row['scene_absolute_number']) if not curEpisode in seasons: seasons[curEpisode] = {} seasons[curEpisode] = row @@ -2384,50 +3813,58 @@ class CMD_ShowSeasons(ApiCall): return _responds(RESULT_SUCCESS, seasons) -class CMD_ShowSetQuality(ApiCall): - _help = { - "desc": "set desired quality of a show in sickbeard. if neither initial or archive are provided then the config default quality will be used", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"} - }, - "optionalParameters": {"initial": {"desc": "initial quality for the show"}, - "archive": {"desc": "archive quality for the show"} - } - } +class CMD_ShowSeasons(CMD_SickGearShowSeasons): + _help = {"desc": "display a listing of episodes for all or a given season", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "optionalParameters": {"season": {"desc": "the season number"}, + }, + "SickGearCommand": "sg.show.seasons", + } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowSeasons.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowSetQuality(ApiCall): + _help = { + "desc": "set desired quality of a show in sickgear. if neither initial or archive are provided then the config default quality will be used", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"} + }, + "optionalParameters": {"initial": {"desc": "initial quality for the show"}, + "archive": {"desc": "archive quality for the show"}, + "upgrade_once": {"desc": "upgrade only once"} + } + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # this for whatever reason removes hdbluray not sdtv... which is just wrong. reverting to previous code.. plus we didnt use the new code everywhere. # self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", _getQualityMap().values()[1:]) - self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", - ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", - "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"]) - self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", - ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", - "hdbluray", "fullhdbluray"]) + self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", [q for q in quality_map]) + self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", [q for q in quality_map]) + self.upgradeonce, args = self.check_params(args, kwargs, "upgrade_once", False, False, "bool", []) # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ set the quality for a show in sickbeard by taking in a deliminated + """ set the quality for a show in sickgear by taking in a deliminated string of qualities, map to their value and combine for new values """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") - quality_map = {'sdtv': Quality.SDTV, - 'sddvd': Quality.SDDVD, - 'hdtv': Quality.HDTV, - 'rawhdtv': Quality.RAWHDTV, - 'fullhdtv': Quality.FULLHDTV, - 'hdwebdl': Quality.HDWEBDL, - 'fullhdwebdl': Quality.FULLHDWEBDL, - 'hdbluray': Quality.HDBLURAY, - 'fullhdbluray': Quality.FULLHDBLURAY, - 'unknown': Quality.UNKNOWN} - #use default quality as a failsafe newQuality = int(sickbeard.QUALITY_DEFAULT) iqualityID = [] @@ -2444,18 +3881,46 @@ class CMD_ShowSetQuality(ApiCall): newQuality = Quality.combineQualities(iqualityID, aqualityID) showObj.quality = newQuality + showObj.upgrade_once = self.upgradeonce + + showObj.saveToDB() + return _responds(RESULT_SUCCESS, msg=showObj.name + " quality has been changed to " + _get_quality_string(showObj.quality)) -class CMD_ShowStats(ApiCall): - _help = {"desc": "display episode statistics for a given show", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, - } - } +class CMD_ShowSetQuality(CMD_SickGearShowSetQuality): + _help = { + "desc": "set desired quality of a show in sickgear. if neither initial or archive are provided then the config default quality will be used", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"} + }, + "optionalParameters": {"initial": {"desc": "initial quality for the show"}, + "archive": {"desc": "archive quality for the show"} + }, + "SickGearCommand": "sg.show.setquality", + } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowSetQuality.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowStats(ApiCall): + _help = {"desc": "display episode statistics for a given show", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help @@ -2463,7 +3928,7 @@ class CMD_ShowStats(ApiCall): def run(self): """ display episode statistics for a given show """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2494,8 +3959,9 @@ class CMD_ShowStats(ApiCall): episode_qualities_counts_snatch[statusCode] = 0 myDB = db.DBConnection(row_type="dict") - sqlResults = myDB.select("SELECT status, season FROM tv_episodes WHERE season != 0 AND showid = ?", - [self.indexerid]) + sqlResults = myDB.select("SELECT status, season FROM tv_episodes WHERE season != 0 AND showid = ? " + "AND indexer = ?", + [self.indexerid, self.indexer]) # the main loop that goes through all episodes for row in sqlResults: status, quality = Quality.splitCompositeStatus(int(row["status"])) @@ -2552,22 +4018,41 @@ class CMD_ShowStats(ApiCall): return _responds(RESULT_SUCCESS, episodes_stats) -class CMD_ShowUpdate(ApiCall): - _help = {"desc": "update a show in sickbeard", - "requiredParameters": {"indexerid": {"desc": "thetvdb.com unique id of a show"}, +class CMD_ShowStats(CMD_SickGearShowStats): + _help = {"desc": "display episode statistics for a given show", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.stats", } - } def __init__(self, handler, args, kwargs): # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowStats.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowUpdate(ApiCall): + _help = {"desc": "update a show in sickgear", + "requiredParameters": {"indexer": {"desc": "indexer of a show"}, + "indexerid": {"desc": "unique id of a show"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.indexer, args = self.check_params(args, kwargs, "indexer", None, True, "int", + [i for i in indexer_api.indexerApi().search_indexers]) self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", []) # optional # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ update a show in sickbeard """ - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.indexerid)) + """ update a show in sickgear """ + showObj = helpers.find_show_by_id(sickbeard.showList, {self.indexer: self.indexerid}) if not showObj: return _responds(RESULT_FAILURE, msg="Show not found") @@ -2579,27 +4064,49 @@ class CMD_ShowUpdate(ApiCall): return _responds(RESULT_FAILURE, msg="Unable to update " + str(showObj.name)) -class CMD_Shows(ApiCall): - _help = {"desc": "display all shows in sickbeard", +class CMD_ShowUpdate(CMD_SickGearShowUpdate): + _help = {"desc": "update a show in sickgear", + "requiredParameters": {"indexerid": {"desc": "thetvdb.com id of a show"}, + }, + "SickGearCommand": "sg.show.update", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + kwargs['indexer'] = INDEXER_TVDB + self.sickbeard_call = True + CMD_SickGearShowUpdate.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShows(ApiCall): + _help = {"desc": "display all shows in sickgear", "optionalParameters": {"sort": {"desc": "sort the list of shows by show name instead of indexerid"}, "paused": {"desc": "only show the shows that are set to paused"}, - }, - } + "overview": {"desc": "include overview"}, + }, + } def __init__(self, handler, args, kwargs): # required # optional self.sort, args = self.check_params(args, kwargs, "sort", "id", False, "string", ["id", "name"]) self.paused, args = self.check_params(args, kwargs, "paused", None, False, "bool", []) + self.overview, args = self.check_params(args, kwargs, "overview", False, False, "bool", []) # super, missing, help ApiCall.__init__(self, handler, args, kwargs) def run(self): - """ display_is_int_multi( self.indexerid )shows in sickbeard """ + """ display_is_int_multi( self.indexerid )shows in sickgear """ shows = {} + for curShow in sickbeard.showList: - if self.paused != None and bool(self.paused) != bool(curShow.paused): + if self.sickbeard_call and INDEXER_TVDB != curShow.indexer: + continue + + if self.paused is not None and bool(self.paused) != bool(curShow.paused): continue showDict = { @@ -2610,17 +4117,35 @@ class CMD_Shows(ApiCall): "sports": curShow.sports, "anime": curShow.anime, "indexerid": curShow.indexerid, + "indexer": curShow.indexer, "tvdbid": curShow.ids.get(INDEXER_TVDB , {'id': 0})['id'], + 'ids': {k: v.get('id') for k, v in curShow.ids.iteritems()}, "tvrage_id": curShow.ids.get(INDEXER_TVRAGE, {'id': 0})['id'], "tvrage_name": curShow.name, "network": curShow.network, "show_name": curShow.name, "status": curShow.status, "subtitles": curShow.subtitles, + "scenenumbering": curShow.is_scene, + "archivefirstmatch": curShow.upgrade_once, + "irgnorewords": curShow.rls_ignore_words, + "requirewords": curShow.rls_require_words, + "tag": curShow.tag, + "imdb_id": curShow.imdbid, + "classification": curShow.classification, + "runtime": curShow.runtime, + "startyear": curShow.startyear, } + if self.overview: + showDict["overview"] = curShow.overview + + timezone, showDict['timezone'] = network_timezones.get_network_timezone(showDict['network'], + return_name=True) + if curShow.nextaired: - dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(curShow.nextaired, curShow.airs, showDict['network'])) + dtEpisodeAirs = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time( + curShow.nextaired, curShow.airs, timezone)) showDict['next_ep_airdate'] = sbdatetime.sbdatetime.sbfdate(dtEpisodeAirs, d_preset=dateFormat) else: showDict['next_ep_airdate'] = '' @@ -2636,9 +4161,120 @@ class CMD_Shows(ApiCall): return _responds(RESULT_SUCCESS, shows) -class CMD_ShowsStats(ApiCall): +class CMD_Shows(CMD_SickGearShows): + _help = {"desc": "display all thetvdb.com shows in sickgear", + "optionalParameters": {"sort": {"desc": "sort the list of shows by show name instead of indexerid"}, + "paused": {"desc": "only show the shows that are set to paused"}, + "overview": {"desc": "include overview"}, + }, + "SickGearCommand": "sg.shows", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearShows.__init__(self, handler, args, kwargs) + + +class CMD_SickGearShowsBrowseTrakt(ApiCall): + _help = {"desc": "browse trakt shows in sickgear", + "requiredParameters": {"type": {"desc": "type to browse: anticipated, newshows, newseasons, popular, " + "trending, recommended, watchlist"}, + }, + "optionalParameters": {"account_id": {"desc": "account_id for recommended, watchlist - " + "see sg.listtraktaccounts"}, + }, + } + + def __init__(self, handler, args, kwargs): + # required + self.type, args = self.check_params(args, kwargs, "type", "anticipated", True, "string", + ["anticipated", "newshows", "newseasons", "popular", "trending", + "recommended", "watchlist"]) + # optional + self.account, args = self.check_params(args, kwargs, "account_id", None, False, "int", + [s for s in sickbeard.TRAKT_ACCOUNTS]) + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ browse trakt shows in sickgear """ + urls = {'anticipated': 'shows/anticipated?limit=%s&' % 100, + 'newshows': '/calendars/all/shows/new/%s/%s?' % (sbdatetime.sbdatetime.sbfdate( + dt=datetime.datetime.now() + datetime.timedelta(days=-16), d_preset='%Y-%m-%d'), 32), + 'newseasons': '/calendars/all/shows/premieres/%s/%s?' % (sbdatetime.sbdatetime.sbfdate( + dt=datetime.datetime.now() + datetime.timedelta(days=-16), d_preset='%Y-%m-%d'), 32), + 'popular': 'shows/popular?limit=%s&' % 100, + 'trending': 'shows/trending?limit=%s&' % 100, + 'recommended': 'recommendations/shows?limit=%s&' % 100, + } + kwargs = {} + if self.type in ('recommended', 'watchlist'): + if not self.account: + return _responds(RESULT_FAILURE, msg='Need Trakt account') + kwargs['send_oauth'] = self.account + urls['watchlist'] = 'users/%s/watchlist/shows?limit=%s&' % (sickbeard.TRAKT_ACCOUNTS[self.account].slug, 100) + try: + data, oldest, newest = NewHomeAddShows.get_trakt_data(urls[self.type], **kwargs) + except Exception as e: + return _responds(RESULT_FAILURE, msg=e.message) + return _responds(RESULT_SUCCESS, data) + + +class CMD_SickGearListTraktAccounts(ApiCall): + _help = {"desc": "list Trakt accounts in sickgear"} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ list Trakt accounts in sickgear """ + accounts = [{'name': v.name, 'account_id': v.account_id} for a, v in sickbeard.TRAKT_ACCOUNTS.iteritems()] + return _responds(RESULT_SUCCESS, accounts) + + +class CMD_SickGearShowsForceUpdate(ApiCall): + _help = {"desc": "force the daily show update now."} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ force the daily show update now """ + if sickbeard.showQueueScheduler.action.isShowUpdateRunning() or sickbeard.showUpdateScheduler.action.amActive: + return _responds(RESULT_FAILURE, msg="show update already running.") + + result = sickbeard.showUpdateScheduler.forceRun() + if result: + return _responds(RESULT_SUCCESS, msg="daily show update started") + return _responds(RESULT_FAILURE, msg="can't start show update currently") + + +class CMD_SickGearShowsQueue(ApiCall): + _help = {"desc": "list the show update queue."} + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + ApiCall.__init__(self, handler, args, kwargs) + + def run(self): + """ list the show update queue """ + return _responds(RESULT_SUCCESS, sickbeard.showQueueScheduler.action.queue_length()) + + +class CMD_SickGearShowsStats(ApiCall): _help = {"desc": "display the global shows and episode stats" - } + } def __init__(self, handler, args, kwargs): # required @@ -2650,68 +4286,148 @@ class CMD_ShowsStats(ApiCall): """ display the global shows and episode stats """ stats = {} + indexer_limit = ('', ' AND indexer = %s' % INDEXER_TVDB)[self.sickbeard_call] myDB = db.DBConnection() today = str(datetime.date.today().toordinal()) - stats["shows_total"] = len(sickbeard.showList) + stats["shows_total"] = (len(sickbeard.showList), + len([x for x in sickbeard.showList if x.indexer == INDEXER_TVDB]))[self.sickbeard_call] stats["shows_active"] = len( - [show for show in sickbeard.showList if show.paused == 0 and show.status != "Ended"]) + [show for show in sickbeard.showList if show.paused == 0 and show.status != "Ended" + and (not self.sickbeard_call or show.indexer == INDEXER_TVDB)]) stats["ep_downloaded"] = myDB.select("SELECT COUNT(*) FROM tv_episodes WHERE status IN (" + ",".join( [str(show) for show in - Quality.DOWNLOADED + Quality.ARCHIVED]) + ") AND season != 0 and episode != 0 AND airdate <= " + today + "")[0][ - 0] + Quality.DOWNLOADED + Quality.ARCHIVED]) + ") AND season != 0 and episode != 0 AND airdate <= " + today + + indexer_limit)[0][0] stats["ep_total"] = myDB.select( "SELECT COUNT(*) FROM tv_episodes WHERE season != 0 AND episode != 0 AND (airdate != 1 OR status IN (" + ",".join( [str(show) for show in Quality.SNATCHED_ANY + Quality.DOWNLOADED + Quality.ARCHIVED]) + - ")) AND airdate <= " + today + " AND status != " + str(IGNORED) + "")[0][0] + ")) AND airdate <= " + today + " AND status != " + str(IGNORED) + indexer_limit)[0][0] return _responds(RESULT_SUCCESS, stats) + +class CMD_ShowsStats(CMD_SickGearShowsStats): + _help = {"desc": "display the global thetvdb.com shows and episode stats", + "SickGearCommand": "sg.shows.stats", + } + + def __init__(self, handler, args, kwargs): + # required + # optional + # super, missing, help + self.sickbeard_call = True + CMD_SickGearShowsStats.__init__(self, handler, args, kwargs) + + # WARNING: never define a cmd call string that contains a "_" (underscore) # this is reserved for cmd indexes used while cmd chaining # WARNING: never define a param name that contains a "." (dot) # this is reserved for cmd namspaces used while cmd chaining + + _functionMaper = {"help": CMD_Help, + "listcommands": CMD_ListCommands, "future": CMD_ComingEpisodes, + "sg.future": CMD_SickGearComingEpisodes, "episode": CMD_Episode, + "sg.episode": CMD_SickGearEpisode, "episode.search": CMD_EpisodeSearch, + "sg.episode.search": CMD_SickGearEpisodeSearch, "episode.setstatus": CMD_EpisodeSetStatus, + "sg.episode.setstatus": CMD_SickGearEpisodeSetStatus, "episode.subtitlesearch": CMD_SubtitleSearch, + "sg.episode.subtitlesearch": CMD_SickGearSubtitleSearch, "exceptions": CMD_Exceptions, + "sg.exceptions": CMD_SickGearExceptions, + 'sg.setexceptions': CMD_SetExceptions, "history": CMD_History, + "sg.history": CMD_SickGearHistory, "history.clear": CMD_HistoryClear, + "sg.history.clear": CMD_SickGearHistoryClear, "history.trim": CMD_HistoryTrim, + "sg.history.trim": CMD_SickGearHistoryTrim, "logs": CMD_Logs, + "sg.logs": CMD_Logs, "sb": CMD_SickBeard, + "sg": CMD_SickGear, "postprocess": CMD_PostProcess, + "sg.postprocess": CMD_SickGearPostProcess, "sb.addrootdir": CMD_SickBeardAddRootDir, + "sg.addrootdir": CMD_SickGearAddRootDir, "sb.checkscheduler": CMD_SickBeardCheckScheduler, + "sg.checkscheduler": CMD_SickGearCheckScheduler, "sb.deleterootdir": CMD_SickBeardDeleteRootDir, + "sg.deleterootdir": CMD_SickGearDeleteRootDir, "sb.forcesearch": CMD_SickBeardForceSearch, + "sg.forcesearch": CMD_SickGearForceSearch, + "sg.searchqueue": CMD_SickGearSearchQueue, "sb.getdefaults": CMD_SickBeardGetDefaults, + "sg.getdefaults": CMD_SickGearGetDefaults, "sb.getmessages": CMD_SickBeardGetMessages, + "sg.getmessages": CMD_SickGearGetMessages, + "sg.getqualities": CMD_SickGearGetQualities, + "sg.getqualitystrings": CMD_SickGearGetqualityStrings, "sb.getrootdirs": CMD_SickBeardGetRootDirs, + "sg.getrootdirs": CMD_SickGearGetRootDirs, "sb.pausebacklog": CMD_SickBeardPauseBacklog, + "sg.pausebacklog": CMD_SickGearPauseBacklog, "sb.ping": CMD_SickBeardPing, + "sg.ping": CMD_SickGearPing, "sb.restart": CMD_SickBeardRestart, + "sg.restart": CMD_SickGearRestart, "sb.searchtvdb": CMD_SickBeardSearchIndexers, + "sg.searchtv": CMD_SickGearSearchIndexers, "sb.setdefaults": CMD_SickBeardSetDefaults, + "sg.setscenenumber": CMD_SickGearSetSceneNumber, + "sg.activatescenenumbering": CMD_SickGearActivateSceneNumber, + "sg.getindexers": CMD_SickGearGetIndexers, + "sg.getindexericon": CMD_SickGearGetIndexerIcon, + "sg.getnetworkicon": CMD_SickGearGetNetworkIcon, "sb.shutdown": CMD_SickBeardShutdown, + "sg.listignorewords": CMD_SickGearListIgnoreWords, + "sg.setignorewords": CMD_SickGearSetIgnoreWords, + "sg.listrequiredwords": CMD_SickGearListRequireWords, + "sg.setrequiredwords": CMD_SickGearSetRequrieWords, "show": CMD_Show, + "sg.show": CMD_SickGearShow, "show.addexisting": CMD_ShowAddExisting, + "sg.show.addexisting": CMD_SickGearShowAddExisting, "show.addnew": CMD_ShowAddNew, + "sg.show.addnew": CMD_SickGearShowAddNew, "show.cache": CMD_ShowCache, + "sg.show.cache": CMD_SickGearShowCache, "show.delete": CMD_ShowDelete, + "sg.show.delete": CMD_SickGearShowDelete, "show.getquality": CMD_ShowGetQuality, + "sg.show.getquality": CMD_SickGearShowGetQuality, "show.getposter": CMD_ShowGetPoster, + "sg.show.getposter": CMD_SickGearShowGetPoster, "show.getbanner": CMD_ShowGetBanner, + "sg.show.getbanner": CMD_SickGearShowGetBanner, + "sg.show.listfanart": CMD_SickGearShowListFanart, + "sg.show.ratefanart": CMD_SickGearShowRateFanart, + "sg.show.getfanart": CMD_SickGearShowGetFanart, "show.pause": CMD_ShowPause, + "sg.show.pause": CMD_SickGearShowPause, "show.refresh": CMD_ShowRefresh, + "sg.show.refresh": CMD_SickGearShowRefresh, "show.seasonlist": CMD_ShowSeasonList, + "sg.show.seasonlist": CMD_SickGearShowSeasonList, "show.seasons": CMD_ShowSeasons, + "sg.show.seasons": CMD_SickGearShowSeasons, "show.setquality": CMD_ShowSetQuality, + "sg.show.setquality": CMD_SickGearShowSetQuality, "show.stats": CMD_ShowStats, + "sg.show.stats": CMD_SickGearShowStats, "show.update": CMD_ShowUpdate, + "sg.show.update": CMD_SickGearShowUpdate, "shows": CMD_Shows, - "shows.stats": CMD_ShowsStats -} + "sg.shows": CMD_SickGearShows, + "sg.shows.browsetrakt": CMD_SickGearShowsBrowseTrakt, + "sg.listtraktaccounts": CMD_SickGearListTraktAccounts, + "shows.stats": CMD_ShowsStats, + "sg.shows.stats": CMD_SickGearShowsStats, + "sg.shows.forceupdate": CMD_SickGearShowsForceUpdate, + "sg.shows.queue": CMD_SickGearShowsQueue, + } diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 19919e62..a9518c63 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -51,7 +51,7 @@ from sickbeard.helpers import has_image_ext, remove_article, starify from sickbeard.indexers.indexer_config import INDEXER_TVDB, INDEXER_TVRAGE, INDEXER_TRAKT from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering, get_scene_numbering_for_show, \ get_xem_numbering_for_show, get_scene_absolute_numbering_for_show, get_xem_absolute_numbering_for_show, \ - get_scene_absolute_numbering + get_scene_absolute_numbering, set_scene_numbering_helper from sickbeard.name_cache import buildNameCache from sickbeard.browser import foldersAtPath from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names @@ -145,6 +145,13 @@ class BaseHandler(RequestHandler): return self.get_secure_cookie('sickgear-session-%s' % helpers.md5_for_text(sickbeard.WEB_PORT)) return True + def getImage(self, image): + if ek.ek(os.path.isfile, image): + mime_type, encoding = MimeTypes().guess_type(image) + self.set_header('Content-Type', mime_type) + with ek.ek(open, image, 'rb') as img: + return img.read() + def showPoster(self, show=None, which=None, api=None): # Redirect initial poster/banner thumb to default images if 'poster' == which[0:6]: @@ -175,9 +182,14 @@ class BaseHandler(RequestHandler): static_image_path = image_file_name if api: + used_file = ek.ek(os.path.basename, static_image_path) + if static_image_path.startswith('/images'): + used_file = 'default' + static_image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', static_image_path[1:]) mime_type, encoding = MimeTypes().guess_type(static_image_path) self.set_header('Content-Type', mime_type) - with open(static_image_path, 'rb') as img: + self.set_header('X-Filename', used_file) + with ek.ek(open, static_image_path, 'rb') as img: return img.read() else: static_image_path = os.path.normpath(static_image_path.replace(sickbeard.CACHE_DIR, '/cache')) @@ -2458,50 +2470,8 @@ class Home(MainHandler): def setSceneNumbering(self, show, indexer, forSeason=None, forEpisode=None, forAbsolute=None, sceneSeason=None, sceneEpisode=None, sceneAbsolute=None): - # sanitize: - show = None if show in [None, 'null', ''] else int(show) - indexer = None if indexer in [None, 'null', ''] else int(indexer) - - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, show) - - if not show_obj.is_anime: - for_season = None if forSeason in [None, 'null', ''] else int(forSeason) - for_episode = None if forEpisode in [None, 'null', ''] else int(forEpisode) - scene_season = None if sceneSeason in [None, 'null', ''] else int(sceneSeason) - scene_episode = None if sceneEpisode in [None, 'null', ''] else int(sceneEpisode) - action_log = u'Set episode scene numbering to %sx%s for episode %sx%s of "%s"'\ - % (scene_season, scene_episode, for_season, for_episode, show_obj.name) - ep_args = {'show': show, 'season': for_season, 'episode': for_episode} - scene_args = {'indexer_id': show, 'indexer': indexer, 'season': for_season, 'episode': for_episode, - 'sceneSeason': scene_season, 'sceneEpisode': scene_episode} - result = {'forSeason': for_season, 'forEpisode': for_episode, 'sceneSeason': None, 'sceneEpisode': None} - else: - for_absolute = None if forAbsolute in [None, 'null', ''] else int(forAbsolute) - scene_absolute = None if sceneAbsolute in [None, 'null', ''] else int(sceneAbsolute) - action_log = u'Set absolute scene numbering to %s for episode %s of "%s"'\ - % (scene_absolute, for_absolute, show_obj.name) - ep_args = {'show': show, 'absolute': for_absolute} - scene_args = {'indexer_id': show, 'indexer': indexer, 'absolute_number': for_absolute, - 'sceneAbsolute': scene_absolute} - result = {'forAbsolute': for_absolute, 'sceneAbsolute': None} - - ep_obj = self._getEpisode(**ep_args) - result['success'] = not isinstance(ep_obj, str) - if result['success']: - logger.log(action_log, logger.DEBUG) - set_scene_numbering(**scene_args) - show_obj.flushEpisodes() - else: - result['errorMessage'] = ep_obj - - if not show_obj.is_anime: - scene_numbering = get_scene_numbering(show, indexer, for_season, for_episode) - if scene_numbering: - (result['sceneSeason'], result['sceneEpisode']) = scene_numbering - else: - scene_numbering = get_scene_absolute_numbering(show, indexer, for_absolute) - if scene_numbering: - result['sceneAbsolute'] = scene_numbering + result = set_scene_numbering_helper(show, indexer, forSeason, forEpisode, forAbsolute, sceneSeason, + sceneEpisode, sceneAbsolute) return json.dumps(result) @@ -2730,7 +2700,7 @@ class NewHomeAddShows(Home): re.sub(r'([,.!][^,.!]*?)$', '...', re.sub(r'([.!?])(?=\w)', r'\1 ', self.encode_html((show.get('overview', '') or '')[:250:].strip()))), - self._get_UWRatio(term, show['seriesname'], show.get('aliases', [])), None, None, + self.get_UWRatio(term, show['seriesname'], show.get('aliases', [])), None, None, self._make_search_image_url(iid, show) ] for show in shows.itervalues()] for iid, shows in results.iteritems())) @@ -2791,7 +2761,8 @@ class NewHomeAddShows(Home): ('%s.jpg' % show['id'], show['id']) return img_url - def _get_UWRatio(self, search_term, showname, aliases): + @classmethod + def get_UWRatio(cls, search_term, showname, aliases): s = fuzz.UWRatio(search_term, showname) # check aliases and give them a little lower score for a in aliases: @@ -3452,15 +3423,9 @@ class NewHomeAddShows(Home): return self.redirect('/home/addShows/%s' % ('trakt_trending', sickbeard.TRAKT_MRU)[any(sickbeard.TRAKT_MRU)]) - def browse_trakt(self, url_path, browse_title, *args, **kwargs): - - browse_type = 'Trakt' + @staticmethod + def get_trakt_data(url_path, *args, **kwargs): normalised, filtered = ([], []) - - if not sickbeard.USE_TRAKT and ('recommended' in kwargs.get('mode', '') or 'watchlist' in kwargs.get('mode', '')): - error_msg = 'To browse personal recommendations, enable Trakt.tv in Config/Notifications/Social' - return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) - error_msg = None try: account = kwargs.get('send_oauth', None) @@ -3488,7 +3453,7 @@ class NewHomeAddShows(Home): if not normalised: error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' - return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) + raise Exception(error_msg) oldest_dt = 9999999 newest_dt = 0 @@ -3496,8 +3461,8 @@ class NewHomeAddShows(Home): newest = None for item in normalised: ignore = ''' - ((bbc|channel\s*?5.*?|itv)\s*?(drama|documentaries))|bbc\s*?(comedy|music)|music\s*?specials|tedtalks - ''' + ((bbc|channel\s*?5.*?|itv)\s*?(drama|documentaries))|bbc\s*?(comedy|music)|music\s*?specials|tedtalks + ''' if re.search(ignore, item['show']['title'].strip(), re.I | re.X): continue try: @@ -3523,25 +3488,54 @@ class NewHomeAddShows(Home): when_past=dt_ordinal < datetime.datetime.now().toordinal(), # air time not yet available 16.11.2015 episode_number='' if 'episode' not in item else item['episode']['number'] or 1, episode_overview=('' if 'episode' not in item else - self.encode_html(item['episode']['overview'][:250:].strip()) or ''), + item['episode']['overview'].strip() or ''), episode_season='' if 'episode' not in item else item['episode']['season'] or 1, genres=('' if 'genres' not in item['show'] else ', '.join(['%s' % v for v in item['show']['genres']])), ids=item['show']['ids'], images=images, overview=('' if 'overview' not in item['show'] or None is item['show']['overview'] else - self.encode_html(item['show']['overview'][:250:].strip())), + item['show']['overview'].strip()), rating=0 < item['show'].get('rating', 0) and ('%.2f' % (item['show'].get('rating') * 10)).replace('.00', '') or 0, title=item['show']['title'].strip(), url_src_db='https://trakt.tv/shows/%s' % item['show']['ids']['slug'], url_tvdb=('', '%s%s' % (sickbeard.indexerApi(INDEXER_TVDB).config['show_url'], - item['show']['ids']['tvdb']))[isinstance(item['show']['ids']['tvdb'], (int, long)) - and 0 < item['show']['ids']['tvdb']], + item['show']['ids']['tvdb']))[ + isinstance(item['show']['ids']['tvdb'], (int, long)) + and 0 < item['show']['ids']['tvdb']], votes='0' if 'votes' not in item['show'] else item['show']['votes'])) - except: + except (StandardError, Exception): pass + if 'web_ui' in kwargs: + return filtered, oldest, newest, error_msg + + return filtered, oldest, newest + + def browse_trakt(self, url_path, browse_title, *args, **kwargs): + + browse_type = 'Trakt' + normalised, filtered = ([], []) + + if not sickbeard.USE_TRAKT and ('recommended' in kwargs.get('mode', '') or 'watchlist' in kwargs.get('mode', '')): + error_msg = 'To browse personal recommendations, enable Trakt.tv in Config/Notifications/Social' + return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) + + try: + filtered, oldest, newest, error_msg = self.get_trakt_data(url_path, web_ui=True) + except (StandardError, Exception): + error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' + return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) + + for item in filtered: + key = 'episode_overview' + if item[key]: + item[key] = self.encode_html(item[key][:250:].strip()) + key = 'overview' + if item[key]: + item[key] = self.encode_html(item[key][:250:].strip()) + kwargs.update(dict(oldest=oldest, newest=newest, error_msg=error_msg)) if 'recommended' not in kwargs.get('mode', '') and 'watchlist' not in kwargs.get('mode', ''): @@ -6233,6 +6227,8 @@ class ApiBuilder(MainHandler): t.seasonSQLResults = seasonSQLResults t.episodeSQLResults = episodeSQLResults + t.indexers = sickbeard.indexerApi().all_indexers + t.searchindexers = sickbeard.indexerApi().search_indexers if len(sickbeard.API_KEY) == 32: t.apikey = sickbeard.API_KEY