From 71d7938fac869225c4b321979df4e11e1de112a1 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 23 Feb 2015 10:19:25 +0800 Subject: [PATCH] Change webserve code to a logical layout and PEP8 --- CHANGES.md | 1 + sickbeard/browser.py | 17 +- sickbeard/webapi.py | 34 - sickbeard/webserve.py | 4635 ++++++++++++++++++------------------- sickbeard/webserveInit.py | 7 +- 5 files changed, 2305 insertions(+), 2389 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0f1d30df..92b6526f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ * Add requirements file for pip (port from midgetspy/sick-beard) * Remove unused libraries fuzzywuzzy and pysrt +* Change webserve code to a logical layout and PEP8 [develop changelog] diff --git a/sickbeard/browser.py b/sickbeard/browser.py index df027315..5b6e6429 100644 --- a/sickbeard/browser.py +++ b/sickbeard/browser.py @@ -19,10 +19,8 @@ import os import string -from tornado.httputil import HTTPHeaders -from tornado.web import RequestHandler from sickbeard import encodingKludge as ek -from sickbeard import logger, webserve +from sickbeard import logger # use the built-in if it's available (python 2.6), if not use the included library try: @@ -104,15 +102,4 @@ def foldersAtPath(path, includeParent=False, includeFiles=False): entries.append({'name': "..", 'path': parentPath}) entries.extend(fileList) - return entries - - -class WebFileBrowser(webserve.MainHandler): - def index(self, path='', includeFiles=False, *args, **kwargs): - self.set_header("Content-Type", "application/json") - return json.dumps(foldersAtPath(path, True, bool(int(includeFiles)))) - - def complete(self, term, includeFiles=0): - self.set_header("Content-Type", "application/json") - paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term), includeFiles=bool(int(includeFiles))) if 'path' in entry] - return json.dumps(paths) \ No newline at end of file + return entries \ No newline at end of file diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py index 7770dc95..7fa3e3ef 100644 --- a/sickbeard/webapi.py +++ b/sickbeard/webapi.py @@ -166,40 +166,6 @@ class Api(webserve.BaseHandler): return False, msg, args, kwargs -class ApiBuilder(webserve.MainHandler): - def index(self): - """ expose the api-builder template """ - t = webserve.PageTemplate(headers=self.request.headers, file="apiBuilder.tmpl") - - def titler(x): - return (remove_article(x), x)[not x or sickbeard.SORT_ARTICLE] - - t.sortedShowList = sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name))) - - seasonSQLResults = {} - episodeSQLResults = {} - - myDB = db.DBConnection(row_type="dict") - for curShow in t.sortedShowList: - seasonSQLResults[curShow.indexerid] = myDB.select( - "SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC", [curShow.indexerid]) - - for curShow in t.sortedShowList: - episodeSQLResults[curShow.indexerid] = myDB.select( - "SELECT DISTINCT season,episode FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC", - [curShow.indexerid]) - - t.seasonSQLResults = seasonSQLResults - t.episodeSQLResults = episodeSQLResults - - if len(sickbeard.API_KEY) == 32: - t.apikey = sickbeard.API_KEY - else: - t.apikey = "api key not generated" - - return webserve._munge(t) - - def call_dispatcher(handler, args, kwargs): """ calls the appropriate CMD class looks for a cmd in args and kwargs diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 82c05862..a275b39a 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -18,35 +18,22 @@ from __future__ import with_statement -import base64 -import inspect -import traceback - import os - import time import urllib import re import datetime import random -import sys +import traceback + +from mimetypes import MimeTypes + +from Cheetah.Template import Template import sickbeard - -from sickbeard import config, sab -from sickbeard import clients -from sickbeard import history, notifiers, processTV -from sickbeard import ui -from sickbeard import logger, helpers, exceptions, classes, db +from sickbeard import config, sab, clients, history, notifiers, processTV, ui, logger, helpers, exceptions, classes, \ + db, search_queue, image_cache, naming, scene_exceptions, subtitles, network_timezones, sbdatetime from sickbeard import encodingKludge as ek -from sickbeard import search_queue -from sickbeard import image_cache -from sickbeard import naming -from sickbeard import scene_exceptions -from sickbeard import subtitles -from sickbeard import network_timezones -from sickbeard import sbdatetime - from sickbeard.providers import newznab, rsstorrent from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets from sickbeard.common import SNATCHED, UNAIRED, IGNORED, ARCHIVED, WANTED, FAILED @@ -57,15 +44,14 @@ from sickbeard.scene_exceptions import get_scene_exceptions 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 - +from sickbeard.browser import foldersAtPath from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names - -from mimetypes import MimeTypes - +from tornado import gen +from tornado.web import RequestHandler, authenticated +from lib import adba +from lib import subliminal from lib.dateutil import tz from lib.unrar2 import RarFile - -from lib import subliminal from lib.trakt import TraktCall try: @@ -73,35 +59,54 @@ try: except ImportError: from lib import simplejson as json -try: - import xml.etree.cElementTree as etree -except ImportError: - import xml.etree.ElementTree as etree -from lib import adba +class PageTemplate(Template): + def __init__(self, headers, *args, **KWs): + KWs['file'] = os.path.join(sickbeard.PROG_DIR, 'gui/' + sickbeard.GUI_NAME + '/interfaces/default/', + KWs['file']) + super(PageTemplate, self).__init__(*args, **KWs) -from Cheetah.Template import Template -from tornado.web import RequestHandler, HTTPError, asynchronous, authenticated -from tornado import gen, escape + self.sbRoot = sickbeard.WEB_ROOT + self.sbHttpPort = sickbeard.WEB_PORT + self.sbHttpsPort = sickbeard.WEB_PORT + self.sbHttpsEnabled = sickbeard.ENABLE_HTTPS + self.sbHandleReverseProxy = sickbeard.HANDLE_REVERSE_PROXY + self.sbThemeName = sickbeard.THEME_NAME + if headers['Host'][0] == '[': + self.sbHost = re.match('^\[.*\]', headers['Host'], re.X | re.M | re.S).group(0) + else: + self.sbHost = re.match('^[^:]+', headers['Host'], re.X | re.M | re.S).group(0) -class HTTPRedirect(Exception): - """Exception raised when the request should be redirected.""" + if 'X-Forwarded-Host' in headers: + self.sbHost = headers['X-Forwarded-Host'] + if 'X-Forwarded-Port' in headers: + sbHttpPort = headers['X-Forwarded-Port'] + self.sbHttpsPort = sbHttpPort + if 'X-Forwarded-Proto' in headers: + self.sbHttpsEnabled = True if headers['X-Forwarded-Proto'] == 'https' else False - def __init__(self, url, permanent=False, status=None): - self.url = url - self.permanent = permanent - self.status = status - Exception.__init__(self, self.url, self.permanent, self.status) + logPageTitle = 'Logs & Errors' + if len(classes.ErrorViewer.errors): + logPageTitle += ' (' + str(len(classes.ErrorViewer.errors)) + ')' + self.logPageTitle = logPageTitle + self.sbPID = str(sickbeard.PID) + self.menu = [ + {'title': 'Home', 'key': 'home'}, + {'title': 'Episodes', 'key': 'episodeView'}, + {'title': 'History', 'key': 'history'}, + {'title': 'Manage', 'key': 'manage'}, + {'title': 'Config', 'key': 'config'}, + {'title': logPageTitle, 'key': 'errorlogs'}, + ] - def __call__(self): - """Use this exception as a request.handler (raise self).""" - raise self + def compile(self, *args, **kwargs): + if not os.path.exists(os.path.join(sickbeard.CACHE_DIR, 'cheetah')): + os.mkdir(os.path.join(sickbeard.CACHE_DIR, 'cheetah')) - -def redirect(url, permanent=False, status=None): - assert url[0] == '/' - raise HTTPRedirect(sickbeard.WEB_ROOT + url, permanent, status) + kwargs['cacheModuleFilesForTracebacks'] = True + kwargs['cacheDirForModuleFiles'] = os.path.join(sickbeard.CACHE_DIR, 'cheetah') + return super(PageTemplate, self).compile(*args, **kwargs) class BaseHandler(RequestHandler): @@ -152,13 +157,7 @@ class BaseHandler(RequestHandler): else: static_image_path = os.path.normpath(static_image_path.replace(sickbeard.CACHE_DIR, '/cache')) static_image_path = static_image_path.replace('\\', '/') - return redirect(static_image_path) - - -class LogoutHandler(BaseHandler): - def get(self, *args, **kwargs): - self.clear_cookie('user') - self.redirect('/login/') + self.redirect(static_image_path) class LoginHandler(BaseHandler): @@ -187,10 +186,98 @@ class LoginHandler(BaseHandler): self.redirect('/login?resp=authfailed' + next_arg) +class LogoutHandler(BaseHandler): + def get(self, *args, **kwargs): + self.clear_cookie('user') + self.redirect('/login/') + + +class CalendarHandler(BaseHandler): + def get(self, *args, **kwargs): + if sickbeard.CALENDAR_UNPROTECTED or self.get_current_user(): + self.write(self.calendar()) + else: + self.set_status(401) + self.write('User authentication required') + + def calendar(self, *args, **kwargs): + """ iCalendar (iCal) - Standard RFC 5545 + Works with iCloud, Google Calendar and Outlook. + Provides a subscribeable URL for iCal subscriptions """ + + logger.log(u'Receiving iCal request from %s' % self.request.remote_ip) + + # Limit dates + past_date = (datetime.date.today() + datetime.timedelta(weeks=-52)).toordinal() + future_date = (datetime.date.today() + datetime.timedelta(weeks=52)).toordinal() + utc = tz.gettz('GMT') + + # Get all the shows that are not paused and are currently on air + myDB = db.DBConnection() + show_list = myDB.select( + 'SELECT show_name, indexer_id, network, airs, runtime FROM tv_shows WHERE ( status = "Continuing" OR status = "Returning Series" ) AND paused != "1"') + + nl = '\\n\\n' + crlf = '\r\n' + + # Create iCal header + appname = 'SickGear' + ical = 'BEGIN:VCALENDAR%sVERSION:2.0%sX-WR-CALNAME:%s%sX-WR-CALDESC:%s%sPRODID://%s Upcoming Episodes//%s'\ + % (crlf, crlf, appname, crlf, appname, crlf, appname, crlf) + + for show in show_list: + # Get all episodes of this show airing between today and next month + episode_list = myDB.select( + 'SELECT indexerid, name, season, episode, description, airdate FROM tv_episodes WHERE airdate >= ? AND airdate < ? AND showid = ?', + (past_date, future_date, int(show['indexer_id']))) + + for episode in episode_list: + + air_date_time = network_timezones.parse_date_time(episode['airdate'], show['airs'], + show['network']).astimezone(utc) + air_date_time_end = air_date_time + datetime.timedelta( + minutes=helpers.tryInt(show['runtime'], 60)) + + # Create event for episode + ical += 'BEGIN:VEVENT%s' % crlf\ + + 'DTSTART:%sT%sZ%s' % (air_date_time.strftime('%Y%m%d'), air_date_time.strftime('%H%M%S'), crlf)\ + + 'DTEND:%sT%sZ%s' % (air_date_time_end.strftime('%Y%m%d'), air_date_time_end.strftime('%H%M%S'), crlf)\ + + 'SUMMARY:%s - %sx%s - %s%s' % (show['show_name'], str(episode['season']), str(episode['episode']), episode['name'], crlf)\ + + 'UID:%s-%s-%s-E%sS%s%s' % (appname, str(datetime.date.today().isoformat()), show['show_name'].replace(' ', '-'), str(episode['episode']), str(episode['season']), crlf)\ + + 'DESCRIPTION:%s on %s' % ((show['airs'] or '(Unknown airs)'), (show['network'] or 'Unknown network'))\ + + ('' if not episode['description'] else '%s%s' % (nl, episode['description'].splitlines()[0]))\ + + '%sEND:VEVENT%s' % (crlf, crlf) + + # Ending the iCal + return ical + 'END:VCALENDAR' + + +class IsAliveHandler(BaseHandler): + def get(self, *args, **kwargs): + kwargs = self.request.arguments + if 'callback' in kwargs and '_' in kwargs: + callback, _ = kwargs['callback'][0], kwargs['_'] + else: + return 'Error: Unsupported Request. Send jsonp request with callback variable in the query string.' + + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + self.set_header('Content-Type', 'text/javascript') + self.set_header('Access-Control-Allow-Origin', '*') + self.set_header('Access-Control-Allow-Headers', 'x-requested-with') + + if sickbeard.started: + results = callback + '(' + json.dumps( + {'msg': str(sickbeard.PID)}) + ');' + else: + results = callback + '(' + json.dumps({'msg': 'nope'}) + ');' + + self.write(results) + + class WebHandler(BaseHandler): def page_not_found(self): t = PageTemplate(headers=self.request.headers, file='404.tmpl') - return _munge(t) + return t.respond() @authenticated @gen.coroutine @@ -205,17 +292,16 @@ class WebHandler(BaseHandler): for arg, value in kwargss.items(): if len(value) == 1: kwargss[arg] = value[0] - try: - self.finish(method(**kwargss)) - except HTTPRedirect, e: - self.redirect(e.url, e.permanent, e.status) + result = method(**kwargss) + if result: + self.finish(result) post = get class MainHandler(WebHandler): def index(self): - return redirect('/home/') + self.redirect('/home/') def http_error_401_handler(self): """ Custom handler for 401 error """ @@ -236,15 +322,15 @@ class MainHandler(WebHandler): self.finish(self.http_error_401_handler()) elif status_code == 404: self.redirect(sickbeard.WEB_ROOT + '/home/') - elif self.settings.get("debug") and "exc_info" in kwargs: - exc_info = kwargs["exc_info"] - trace_info = ''.join(["%s
" % line for line in traceback.format_exception(*exc_info)]) - request_info = ''.join(["%s: %s
" % (k, self.request.__dict__[k] ) for k in + elif self.settings.get('debug') and 'exc_info' in kwargs: + exc_info = kwargs['exc_info'] + trace_info = ''.join(['%s
' % line for line in traceback.format_exception(*exc_info)]) + request_info = ''.join(['%s: %s
' % (k, self.request.__dict__[k] ) for k in self.request.__dict__.keys()]) error = exc_info[1] self.set_header('Content-Type', 'text/html') - self.finish(""" + self.finish(''' %s

Error

@@ -254,13 +340,13 @@ class MainHandler(WebHandler):

Request Info

%s

- """ % (error, error, + ''' % (error, error, trace_info, request_info)) def robots_txt(self, *args, **kwargs): """ Keep web crawlers out """ self.set_header('Content-Type', 'text/plain') - return "User-agent: *\nDisallow: /" + return 'User-agent: *\nDisallow: /' def setHomeLayout(self, layout): @@ -269,7 +355,7 @@ class MainHandler(WebHandler): sickbeard.HOME_LAYOUT = layout - redirect("/home/") + self.redirect('/home/') def setPosterSortBy(self, sort): @@ -291,13 +377,13 @@ class MainHandler(WebHandler): sickbeard.HISTORY_LAYOUT = layout - redirect("/history/") + self.redirect('/history/') def toggleDisplayShowSpecials(self, show): sickbeard.DISPLAY_SHOW_SPECIALS = not sickbeard.DISPLAY_SHOW_SPECIALS - redirect("/home/displayShow?show=" + show) + self.redirect('/home/displayShow?show=' + show) def setEpisodeViewLayout(self, layout): if layout not in ('poster', 'banner', 'list', 'daybyday'): @@ -310,7 +396,7 @@ class MainHandler(WebHandler): sickbeard.save_config() - redirect("/episodeView/") + self.redirect('/episodeView/') def toggleEpisodeViewDisplayPaused(self, *args, **kwargs): @@ -318,7 +404,7 @@ class MainHandler(WebHandler): sickbeard.save_config() - redirect("/episodeView/") + self.redirect('/episodeView/') def setEpisodeViewSort(self, sort, redir=1): if sort not in ('time', 'network', 'show'): @@ -329,9 +415,9 @@ class MainHandler(WebHandler): sickbeard.save_config() if int(redir): - redirect("/episodeView/") + self.redirect('/episodeView/') - def episodeView(self, layout="None"): + def episodeView(self, layout='None'): """ display the episodes """ today_dt = datetime.date.today() #today = today_dt.toordinal() @@ -409,270 +495,1861 @@ class MainHandler(WebHandler): else: t.layout = sickbeard.EPISODE_VIEW_LAYOUT - return _munge(t) + return t.respond() def _genericMessage(self, subject, message): - t = PageTemplate(headers=self.request.headers, file="genericMessage.tmpl") - t.submenu = HomeMenu() + t = PageTemplate(headers=self.request.headers, file='genericMessage.tmpl') + t.submenu = self.HomeMenu() t.subject = subject t.message = message - return _munge(t) + return t.respond() -class CalendarHandler(BaseHandler): - def get(self, *args, **kwargs): - if sickbeard.CALENDAR_UNPROTECTED or self.get_current_user(): - self.write(self.calendar()) - else: - self.set_status(401) - self.write('User authentication required') - - def calendar(self, *args, **kwargs): - """ iCalendar (iCal) - Standard RFC 5545 - Works with iCloud, Google Calendar and Outlook. - Provides a subscribeable URL for iCal subscriptions """ - - logger.log(u'Receiving iCal request from %s' % self.request.remote_ip) - - # Limit dates - past_date = (datetime.date.today() + datetime.timedelta(weeks=-52)).toordinal() - future_date = (datetime.date.today() + datetime.timedelta(weeks=52)).toordinal() - utc = tz.gettz('GMT') - - # Get all the shows that are not paused and are currently on air - myDB = db.DBConnection() - show_list = myDB.select( - 'SELECT show_name, indexer_id, network, airs, runtime FROM tv_shows WHERE ( status = "Continuing" OR status = "Returning Series" ) AND paused != "1"') - - nl = "\\n\\n" - crlf = "\r\n" - - # Create iCal header - appname = 'SickGear' - ical = 'BEGIN:VCALENDAR%sVERSION:2.0%sX-WR-CALNAME:%s%sX-WR-CALDESC:%s%sPRODID://%s Upcoming Episodes//%s'\ - % (crlf, crlf, appname, crlf, appname, crlf, appname, crlf) - - for show in show_list: - # Get all episodes of this show airing between today and next month - episode_list = myDB.select( - 'SELECT indexerid, name, season, episode, description, airdate FROM tv_episodes WHERE airdate >= ? AND airdate < ? AND showid = ?', - (past_date, future_date, int(show['indexer_id']))) - - for episode in episode_list: - - air_date_time = network_timezones.parse_date_time(episode['airdate'], show['airs'], - show['network']).astimezone(utc) - air_date_time_end = air_date_time + datetime.timedelta( - minutes=helpers.tryInt(show['runtime'], 60)) - - # Create event for episode - ical += 'BEGIN:VEVENT%s' % crlf\ - + 'DTSTART:%sT%sZ%s' % (air_date_time.strftime('%Y%m%d'), air_date_time.strftime('%H%M%S'), crlf)\ - + 'DTEND:%sT%sZ%s' % (air_date_time_end.strftime('%Y%m%d'), air_date_time_end.strftime('%H%M%S'), crlf)\ - + 'SUMMARY:%s - %sx%s - %s%s' % (show['show_name'], str(episode['season']), str(episode['episode']), episode['name'], crlf)\ - + 'UID:%s-%s-%s-E%sS%s%s' % (appname, str(datetime.date.today().isoformat()), show['show_name'].replace(' ', '-'), str(episode['episode']), str(episode['season']), crlf)\ - + 'DESCRIPTION:%s on %s' % ((show['airs'] or '(Unknown airs)'), (show['network'] or 'Unknown network'))\ - + ('' if not episode['description'] else '%s%s' % (nl, episode['description'].splitlines()[0]))\ - + '%sEND:VEVENT%s' % (crlf, crlf) - - # Ending the iCal - return ical + 'END:VCALENDAR' - - -class IsAliveHandler(BaseHandler): - def get(self, *args, **kwargs): - kwargs = self.request.arguments - if 'callback' in kwargs and '_' in kwargs: - callback, _ = kwargs['callback'][0], kwargs['_'] - else: - return "Error: Unsupported Request. Send jsonp request with 'callback' variable in the query string." - - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - self.set_header('Content-Type', 'text/javascript') - self.set_header('Access-Control-Allow-Origin', '*') - self.set_header('Access-Control-Allow-Headers', 'x-requested-with') - - if sickbeard.started: - results = callback + '(' + json.dumps( - {"msg": str(sickbeard.PID)}) + ');' - else: - results = callback + '(' + json.dumps({"msg": "nope"}) + ');' - - self.write(results) - - -class PageTemplate(Template): - def __init__(self, headers, *args, **KWs): - KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/", - KWs['file']) - super(PageTemplate, self).__init__(*args, **KWs) - - self.sbRoot = sickbeard.WEB_ROOT - self.sbHttpPort = sickbeard.WEB_PORT - self.sbHttpsPort = sickbeard.WEB_PORT - self.sbHttpsEnabled = sickbeard.ENABLE_HTTPS - self.sbHandleReverseProxy = sickbeard.HANDLE_REVERSE_PROXY - self.sbThemeName = sickbeard.THEME_NAME - - if headers['Host'][0] == '[': - self.sbHost = re.match("^\[.*\]", headers['Host'], re.X | re.M | re.S).group(0) - else: - self.sbHost = re.match("^[^:]+", headers['Host'], re.X | re.M | re.S).group(0) - - if "X-Forwarded-Host" in headers: - self.sbHost = headers['X-Forwarded-Host'] - if "X-Forwarded-Port" in headers: - sbHttpPort = headers['X-Forwarded-Port'] - self.sbHttpsPort = sbHttpPort - if "X-Forwarded-Proto" in headers: - self.sbHttpsEnabled = True if headers['X-Forwarded-Proto'] == 'https' else False - - logPageTitle = 'Logs & Errors' - if len(classes.ErrorViewer.errors): - logPageTitle += ' (' + str(len(classes.ErrorViewer.errors)) + ')' - self.logPageTitle = logPageTitle - self.sbPID = str(sickbeard.PID) - self.menu = [ - {'title': 'Home', 'key': 'home'}, - {'title': 'Episodes', 'key': 'episodeView'}, - {'title': 'History', 'key': 'history'}, - {'title': 'Manage', 'key': 'manage'}, - {'title': 'Config', 'key': 'config'}, - {'title': logPageTitle, 'key': 'errorlogs'}, +class Home(MainHandler): + def HomeMenu(self): + return [ + {'title': 'Add Shows', 'path': 'home/addShows/', }, + {'title': 'Manual Post-Processing', 'path': 'home/postprocess/'}, + {'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': self.haveXBMC}, + {'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': self.havePLEX}, + {'title': 'Manage Torrents', 'path': 'manage/manageTorrents', 'requires': self.haveTORRENT}, + {'title': 'Restart', 'path': 'home/restart/?pid=' + str(sickbeard.PID), 'confirm': True}, + {'title': 'Shutdown', 'path': 'home/shutdown/?pid=' + str(sickbeard.PID), 'confirm': True}, ] - def compile(self, *args, **kwargs): - if not os.path.exists(os.path.join(sickbeard.CACHE_DIR, 'cheetah')): - os.mkdir(os.path.join(sickbeard.CACHE_DIR, 'cheetah')) + @staticmethod + def haveXBMC(): + return sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY - kwargs['cacheModuleFilesForTracebacks'] = True - kwargs['cacheDirForModuleFiles'] = os.path.join(sickbeard.CACHE_DIR, 'cheetah') - return super(PageTemplate, self).compile(*args, **kwargs) + @staticmethod + def havePLEX(): + return sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY - -class IndexerWebUI(MainHandler): - def __init__(self, config, log=None): - self.config = config - self.log = log - - def selectSeries(self, allSeries): - searchList = ",".join([x['id'] for x in allSeries]) - showDirList = "" - for curShowDir in self.config['_showDir']: - showDirList += "showDir=" + curShowDir + "&" - redirect("/home/addShows/addShow?" + showDirList + "seriesList=" + searchList) - - -def _munge(string): - return unicode(string).encode('utf-8', 'xmlcharrefreplace') - - -def _getEpisode(show, season=None, episode=None, absolute=None): - if show is None: - return "Invalid show parameters" - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return "Invalid show paramaters" - - if absolute: - epObj = showObj.getEpisode(absolute_number=int(absolute)) - elif season and episode: - epObj = showObj.getEpisode(int(season), int(episode)) - else: - return "Invalid paramaters" - - if epObj is None: - return "Episode couldn't be retrieved" - - return epObj - - -def ManageMenu(): - manageMenu = [ - {'title': 'Backlog Overview', 'path': 'manage/backlogOverview/'}, - {'title': 'Manage Searches', 'path': 'manage/manageSearches/'}, - {'title': 'Episode Status Management', 'path': 'manage/episodeStatuses/'}, ] - - if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \ - and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https' - or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'): - manageMenu.append({'title': 'Manage Torrents', 'path': 'manage/manageTorrents/'}) - - if sickbeard.USE_SUBTITLES: - manageMenu.append({'title': 'Missed Subtitle Management', 'path': 'manage/subtitleMissed/'}) - - if sickbeard.USE_FAILED_DOWNLOADS: - manageMenu.append({'title': 'Failed Downloads', 'path': 'manage/failedDownloads/'}) - - return manageMenu - - -class ManageSearches(MainHandler): - def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="manage_manageSearches.tmpl") - # t.backlogPI = sickbeard.backlogSearchScheduler.action.getProgressIndicator() - t.backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused() # @UndefinedVariable - t.backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress() # @UndefinedVariable - t.recentSearchStatus = sickbeard.recentSearchScheduler.action.amActive # @UndefinedVariable - t.findPropersStatus = sickbeard.properFinderScheduler.action.amActive # @UndefinedVariable - t.queueLength = sickbeard.searchQueueScheduler.action.queue_length() - - t.submenu = ManageMenu() - - return _munge(t) - - def forceVersionCheck(self, *args, **kwargs): - # force a check to see if there is a new version - if sickbeard.versionCheckScheduler.action.check_for_new_version(force=True): - logger.log(u"Forcing version check") - - redirect("/home/") - - def forceBacklog(self, *args, **kwargs): - # force it to run the next time it looks - result = sickbeard.backlogSearchScheduler.forceRun() - if result: - logger.log(u"Backlog search forced") - ui.notifications.message('Backlog search started') - - redirect("/manage/manageSearches/") - - def forceSearch(self, *args, **kwargs): - - # force it to run the next time it looks - result = sickbeard.recentSearchScheduler.forceRun() - if result: - logger.log(u"Recent search forced") - ui.notifications.message('Recent search started') - - redirect("/manage/manageSearches/") - - def forceFindPropers(self, *args, **kwargs): - - # force it to run the next time it looks - result = sickbeard.properFinderScheduler.forceRun() - if result: - logger.log(u"Find propers search forced") - ui.notifications.message('Find propers search started') - - redirect("/manage/manageSearches/") - - def pauseBacklog(self, paused=None): - if paused == "1": - sickbeard.searchQueueScheduler.action.pause_backlog() # @UndefinedVariable + @staticmethod + def haveTORRENT(): + if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \ + and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https' + or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'): + return True else: - sickbeard.searchQueueScheduler.action.unpause_backlog() # @UndefinedVariable + return False - redirect("/manage/manageSearches/") + @staticmethod + def _getEpisode(show, season=None, episode=None, absolute=None): + if show is None: + return 'Invalid show parameters' + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return 'Invalid show paramaters' + + if absolute: + epObj = showObj.getEpisode(absolute_number=int(absolute)) + elif season and episode: + epObj = showObj.getEpisode(int(season), int(episode)) + else: + return 'Invalid paramaters' + + if epObj is None: + return "Episode couldn't be retrieved" + + return epObj + + def index(self, *args, **kwargs): + + t = PageTemplate(headers=self.request.headers, file='home.tmpl') + if sickbeard.ANIME_SPLIT_HOME: + shows = [] + anime = [] + for show in sickbeard.showList: + if show.is_anime: + anime.append(show) + else: + shows.append(show) + t.showlists = [['Shows', shows], + ['Anime', anime]] + else: + t.showlists = [['Shows', sickbeard.showList]] + + t.submenu = self.HomeMenu() + + return t.respond() + + def testSABnzbd(self, host=None, username=None, password=None, apikey=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_url(host) + + connection, accesMsg = sab.getSabAccesMethod(host, username, password, apikey) + if connection: + authed, authMsg = sab.testAuthentication(host, username, password, apikey) # @UnusedVariable + if authed: + return 'Success. Connected and authenticated' + else: + return "Authentication failed. SABnzbd expects '" + accesMsg + "' as authentication method" + else: + return 'Unable to connect to host' + + def testTorrent(self, torrent_method=None, host=None, username=None, password=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_url(host) + + client = clients.getClientIstance(torrent_method) + + connection, accesMsg = client(host, username, password).testAuthentication() + + return accesMsg + + def testGrowl(self, host=None, password=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_host(host, default_port=23053) + + result = notifiers.growl_notifier.test_notify(host, password) + if password is None or password == '': + pw_append = '' + else: + pw_append = ' with password: ' + password + + if result: + return 'Registered and Tested growl successfully ' + urllib.unquote_plus(host) + pw_append + else: + return 'Registration and Testing of growl failed ' + urllib.unquote_plus(host) + pw_append + + def testProwl(self, prowl_api=None, prowl_priority=0): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.prowl_notifier.test_notify(prowl_api, prowl_priority) + if result: + return 'Test prowl notice sent successfully' + else: + return 'Test prowl notice failed' + + def testBoxcar2(self, accesstoken=None, sound=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.boxcar2_notifier.test_notify(accesstoken, sound) + if result: + return 'Boxcar2 notification succeeded. Check your Boxcar2 clients to make sure it worked' + else: + return 'Error sending Boxcar2 notification' + + def testPushover(self, userKey=None, apiKey=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.pushover_notifier.test_notify(userKey, apiKey) + if result: + return 'Pushover notification succeeded. Check your Pushover clients to make sure it worked' + else: + return 'Error sending Pushover notification' + + def twitterStep1(self, *args, **kwargs): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + return notifiers.twitter_notifier._get_authorization() + + def twitterStep2(self, key): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.twitter_notifier._get_credentials(key) + logger.log(u'result: ' + str(result)) + if result: + return 'Key verification successful' + else: + return 'Unable to verify key' + + def testTwitter(self, *args, **kwargs): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.twitter_notifier.test_notify() + if result: + return 'Tweet successful, check your twitter to make sure it worked' + else: + return 'Error sending tweet' + + def testXBMC(self, host=None, username=None, password=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_hosts(host) + finalResult = '' + for curHost in [x.strip() for x in host.split(',')]: + curResult = notifiers.xbmc_notifier.test_notify(urllib.unquote_plus(curHost), username, password) + if len(curResult.split(':')) > 2 and 'OK' in curResult.split(':')[2]: + finalResult += 'Test XBMC notice sent successfully to ' + urllib.unquote_plus(curHost) + else: + finalResult += 'Test XBMC notice failed to ' + urllib.unquote_plus(curHost) + finalResult += "
\n" + + return finalResult + + def testPMC(self, host=None, username=None, password=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + finalResult = '' + for curHost in [x.strip() for x in host.split(',')]: + curResult = notifiers.plex_notifier.test_notify_pmc(urllib.unquote_plus(curHost), username, password) + if len(curResult.split(':')) > 2 and 'OK' in curResult.split(':')[2]: + finalResult += 'Successful test notice sent to Plex client ... ' + urllib.unquote_plus(curHost) + else: + finalResult += 'Test failed for Plex client ... ' + urllib.unquote_plus(curHost) + finalResult += '
' + '\n' + + ui.notifications.message('Tested Plex client(s): ', urllib.unquote_plus(host.replace(',', ', '))) + + return finalResult + + def testPMS(self, host=None, username=None, password=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + finalResult = '' + + curResult = notifiers.plex_notifier.test_notify_pms(urllib.unquote_plus(host), username, password) + if None is curResult: + finalResult += 'Successful test of Plex server(s) ... ' + urllib.unquote_plus(host.replace(',', ', ')) + else: + finalResult += 'Test failed for Plex server(s) ... ' + urllib.unquote_plus(curResult.replace(',', ', ')) + finalResult += '
' + '\n' + + ui.notifications.message('Tested Plex Media Server host(s): ', urllib.unquote_plus(host.replace(',', ', '))) + + return finalResult + + def testLibnotify(self, *args, **kwargs): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + if notifiers.libnotify_notifier.test_notify(): + return 'Tried sending desktop notification via libnotify' + else: + return notifiers.libnotify.diagnose() + + def testNMJ(self, host=None, database=None, mount=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_host(host) + result = notifiers.nmj_notifier.test_notify(urllib.unquote_plus(host), database, mount) + if result: + return 'Successfully started the scan update' + else: + return 'Test failed to start the scan update' + + def settingsNMJ(self, host=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_host(host) + result = notifiers.nmj_notifier.notify_settings(urllib.unquote_plus(host)) + if result: + return '{"message": "Got settings from %(host)s", "database": "%(database)s", "mount": "%(mount)s"}' % { + "host": host, "database": sickbeard.NMJ_DATABASE, "mount": sickbeard.NMJ_MOUNT} + else: + return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}' + + def testNMJv2(self, host=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_host(host) + result = notifiers.nmjv2_notifier.test_notify(urllib.unquote_plus(host)) + if result: + return 'Test notice sent successfully to ' + urllib.unquote_plus(host) + else: + return 'Test notice failed to ' + urllib.unquote_plus(host) + + def settingsNMJv2(self, host=None, dbloc=None, instance=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_host(host) + result = notifiers.nmjv2_notifier.notify_settings(urllib.unquote_plus(host), dbloc, instance) + if result: + return '{"message": "NMJ Database found at: %(host)s", "database": "%(database)s"}' % {"host": host, + "database": sickbeard.NMJv2_DATABASE} + else: + return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % { + "dbloc": dbloc} + + def testTrakt(self, api=None, username=None, password=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.trakt_notifier.test_notify(api, username, password) + if result: + return 'Test notice sent successfully to Trakt' + else: + return 'Test notice failed to Trakt' + + def loadShowNotifyLists(self, *args, **kwargs): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + myDB = db.DBConnection() + rows = myDB.select('SELECT show_id, show_name, notify_list FROM tv_shows ORDER BY show_name ASC') + + data = {} + size = 0 + for r in rows: + data[r['show_id']] = {'id': r['show_id'], 'name': r['show_name'], 'list': r['notify_list']} + size += 1 + data['_size'] = size + return json.dumps(data) + + def testEmail(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + host = config.clean_host(host) + if notifiers.email_notifier.test_notify(host, port, smtp_from, use_tls, user, pwd, to): + return 'Test email sent successfully! Check inbox.' + else: + return 'ERROR: %s' % notifiers.email_notifier.last_err + + def testNMA(self, nma_api=None, nma_priority=0): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.nma_notifier.test_notify(nma_api, nma_priority) + if result: + return 'Test NMA notice sent successfully' + else: + return 'Test NMA notice failed' + + def testPushalot(self, authorizationToken=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.pushalot_notifier.test_notify(authorizationToken) + if result: + return 'Pushalot notification succeeded. Check your Pushalot clients to make sure it worked' + else: + return 'Error sending Pushalot notification' + + def testPushbullet(self, api=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.pushbullet_notifier.test_notify(api) + if result: + return 'Pushbullet notification succeeded. Check your device to make sure it worked' + else: + return 'Error sending Pushbullet notification' + + def getPushbulletDevices(self, api=None): + self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + + result = notifiers.pushbullet_notifier.get_devices(api) + if result: + return result + else: + return 'Error sending Pushbullet notification' + + def shutdown(self, pid=None): + + if str(pid) != str(sickbeard.PID): + self.redirect('/home/') + + sickbeard.events.put(sickbeard.events.SystemEvent.SHUTDOWN) + + title = 'Shutting down' + message = 'SickGear is shutting down...' + + return self._genericMessage(title, message) + + def restart(self, pid=None): + + if str(pid) != str(sickbeard.PID): + self.redirect('/home/') + + t = PageTemplate(headers=self.request.headers, file='restart.tmpl') + t.submenu = self.HomeMenu() + + # restart + sickbeard.events.put(sickbeard.events.SystemEvent.RESTART) + + return t.respond() + + def update(self, pid=None): + + if str(pid) != str(sickbeard.PID): + self.redirect('/home/') + + updated = sickbeard.versionCheckScheduler.action.update() # @UndefinedVariable + if updated: + # do a hard restart + sickbeard.events.put(sickbeard.events.SystemEvent.RESTART) + + t = PageTemplate(headers=self.request.headers, file='restart_bare.tmpl') + return t.respond() + else: + return self._genericMessage('Update Failed', + "Update wasn't successful, not restarting. Check your log for more information.") + + def branchCheckout(self, branch): + sickbeard.BRANCH = branch + ui.notifications.message('Checking out branch: ', branch) + return self.update(sickbeard.PID) + + def pullRequestCheckout(self, branch): + pull_request = branch + branch = branch.split(':')[1] + fetched = sickbeard.versionCheckScheduler.action.fetch(pull_request) + if fetched: + sickbeard.BRANCH = branch + ui.notifications.message('Checking out branch: ', branch) + return self.update(sickbeard.PID) + else: + self.redirect('/home/') + + def displayShow(self, show=None): + + if show is None: + return self._genericMessage('Error', 'Invalid show ID') + else: + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return self._genericMessage('Error', 'Show not in show list') + + myDB = db.DBConnection() + seasonResults = myDB.select( + 'SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season desc', + [showObj.indexerid] + ) + + sqlResults = myDB.select( + 'SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC', + [showObj.indexerid] + ) + + t = PageTemplate(headers=self.request.headers, file='displayShow.tmpl') + t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}] + + try: + t.showLoc = (showObj.location, True) + except sickbeard.exceptions.ShowDirNotFoundException: + t.showLoc = (showObj._location, False) + + show_message = '' + + if sickbeard.showQueueScheduler.action.isBeingAdded(showObj): # @UndefinedVariable + show_message = 'This show is in the process of being downloaded - the info below is incomplete.' + + elif sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable + show_message = 'The information on this page is in the process of being updated.' + + elif sickbeard.showQueueScheduler.action.isBeingRefreshed(showObj): # @UndefinedVariable + show_message = 'The episodes below are currently being refreshed from disk' + + elif sickbeard.showQueueScheduler.action.isBeingSubtitled(showObj): # @UndefinedVariable + show_message = 'Currently downloading subtitles for this show' + + elif sickbeard.showQueueScheduler.action.isInRefreshQueue(showObj): # @UndefinedVariable + show_message = 'This show is queued to be refreshed.' + + elif sickbeard.showQueueScheduler.action.isInUpdateQueue(showObj): # @UndefinedVariable + show_message = 'This show is queued and awaiting an update.' + + elif sickbeard.showQueueScheduler.action.isInSubtitleQueue(showObj): # @UndefinedVariable + show_message = 'This show is queued and awaiting subtitles download.' + + if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj): # @UndefinedVariable + if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable + t.submenu.append( + {'title': 'Remove', 'path': 'home/deleteShow?show=%d' % showObj.indexerid, 'confirm': True}) + t.submenu.append({'title': 'Re-scan files', 'path': 'home/refreshShow?show=%d' % showObj.indexerid}) + t.submenu.append( + {'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&force=1' % showObj.indexerid}) + t.submenu.append({'title': 'Update show in XBMC', + 'path': 'home/updateXBMC?showName=%s' % urllib.quote_plus( + showObj.name.encode('utf-8')), 'requires': self.haveXBMC}) + t.submenu.append({'title': 'Preview Rename', 'path': 'home/testRename?show=%d' % showObj.indexerid}) + if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled( + showObj) and showObj.subtitles: + t.submenu.append( + {'title': 'Download Subtitles', 'path': 'home/subtitleShow?show=%d' % showObj.indexerid}) + + t.show = showObj + t.sqlResults = sqlResults + t.seasonResults = seasonResults + t.show_message = show_message + + epCounts = {} + epCats = {} + epCounts[Overview.SKIPPED] = 0 + epCounts[Overview.WANTED] = 0 + epCounts[Overview.QUAL] = 0 + epCounts[Overview.GOOD] = 0 + epCounts[Overview.UNAIRED] = 0 + epCounts[Overview.SNATCHED] = 0 + + for curResult in sqlResults: + curEpCat = showObj.getOverview(int(curResult['status'])) + if curEpCat: + epCats[str(curResult['season']) + 'x' + str(curResult['episode'])] = curEpCat + epCounts[curEpCat] += 1 + + def titler(x): + return (remove_article(x), x)[not x or sickbeard.SORT_ARTICLE] + + if sickbeard.ANIME_SPLIT_HOME: + shows = [] + anime = [] + for show in sickbeard.showList: + if show.is_anime: + anime.append(show) + else: + shows.append(show) + t.sortedShowLists = [['Shows', sorted(shows, lambda x, y: cmp(titler(x.name), titler(y.name)))], + ['Anime', sorted(anime, lambda x, y: cmp(titler(x.name), titler(y.name)))]] + + else: + t.sortedShowLists = [ + ['Shows', sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))]] + + tvshows = [] + for tvshow_types in t.sortedShowLists: + for tvshow in tvshow_types[1]: + tvshows.append(tvshow.indexerid) + t.tvshow_id_csv = ','.join(str(x) for x in tvshows) + + t.bwl = None + if showObj.is_anime: + t.bwl = showObj.release_groups + + t.epCounts = epCounts + t.epCats = epCats + + showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid) + + indexerid = int(showObj.indexerid) + indexer = int(showObj.indexer) + t.all_scene_exceptions = showObj.exceptions + t.scene_numbering = get_scene_numbering_for_show(indexerid, indexer) + t.xem_numbering = get_xem_numbering_for_show(indexerid, indexer) + t.scene_absolute_numbering = get_scene_absolute_numbering_for_show(indexerid, indexer) + t.xem_absolute_numbering = get_xem_absolute_numbering_for_show(indexerid, indexer) + + return t.respond() + + def plotDetails(self, show, season, episode): + myDB = db.DBConnection() + result = myDB.select( + 'SELECT description FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?', + (int(show), int(season), int(episode))) + return result[0]['description'] if result else 'Episode not found.' + + def sceneExceptions(self, show): + exceptionsList = sickbeard.scene_exceptions.get_all_scene_exceptions(show) + if not exceptionsList: + return 'No scene exceptions' + + out = [] + for season, names in iter(sorted(exceptionsList.iteritems())): + if season == -1: + season = '*' + out.append('S' + str(season) + ': ' + ', '.join(names)) + return '
'.join(out) + + def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[], + flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None, + indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None, + rls_require_words=None, anime=None, blacklist=None, whitelist=None, + scene=None): + + if show is None: + errString = 'Invalid show ID: ' + str(show) + if directCall: + return [errString] + else: + return self._genericMessage('Error', errString) + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if not showObj: + errString = 'Unable to find the specified show: ' + str(show) + if directCall: + return [errString] + else: + return self._genericMessage('Error', errString) + + showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid) + + if not location and not anyQualities and not bestQualities and not flatten_folders: + t = PageTemplate(headers=self.request.headers, file='editShow.tmpl') + t.submenu = self.HomeMenu() + + if showObj.is_anime: + t.whitelist = showObj.release_groups.whitelist + t.blacklist = showObj.release_groups.blacklist + + t.groups = [] + if helpers.set_up_anidb_connection(): + try: + anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=showObj.name) + t.groups = anime.get_groups() + except Exception, e: + t.groups.append(dict([('name', 'Fail:AniDB connect. Restart sg else check debug log'), ('rating', ''), ('range', '')])) + else: + t.groups.append(dict([('name', 'Did not initialise AniDB. Check debug log if reqd.'), ('rating', ''), ('range', '')])) + + with showObj.lock: + t.show = showObj + t.scene_exceptions = get_scene_exceptions(showObj.indexerid) + + return t.respond() + + flatten_folders = config.checkbox_to_value(flatten_folders) + dvdorder = config.checkbox_to_value(dvdorder) + archive_firstmatch = config.checkbox_to_value(archive_firstmatch) + paused = config.checkbox_to_value(paused) + air_by_date = config.checkbox_to_value(air_by_date) + scene = config.checkbox_to_value(scene) + sports = config.checkbox_to_value(sports) + anime = config.checkbox_to_value(anime) + subtitles = config.checkbox_to_value(subtitles) + + if indexerLang and indexerLang in sickbeard.indexerApi(showObj.indexer).indexer().config['valid_languages']: + indexer_lang = indexerLang + else: + indexer_lang = showObj.lang + + # if we changed the language then kick off an update + if indexer_lang == showObj.lang: + do_update = False + else: + do_update = True + + if scene == showObj.scene and anime == showObj.anime: + do_update_scene_numbering = False + else: + do_update_scene_numbering = True + + if type(anyQualities) != list: + anyQualities = [anyQualities] + + if type(bestQualities) != list: + bestQualities = [bestQualities] + + if type(exceptions_list) != list: + exceptions_list = [exceptions_list] + + # If directCall from mass_edit_update no scene exceptions handling or blackandwhite list handling + if directCall: + do_update_exceptions = False + else: + if set(exceptions_list) == set(showObj.exceptions): + do_update_exceptions = False + else: + do_update_exceptions = True + + with showObj.lock: + if anime: + if not showObj.release_groups: + showObj.release_groups = BlackAndWhiteList(showObj.indexerid) + if whitelist: + shortwhitelist = short_group_names(whitelist) + showObj.release_groups.set_white_keywords(shortwhitelist) + else: + showObj.release_groups.set_white_keywords([]) + + if blacklist: + shortblacklist = short_group_names(blacklist) + showObj.release_groups.set_black_keywords(shortblacklist) + else: + showObj.release_groups.set_black_keywords([]) + + errors = [] + with showObj.lock: + newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities)) + showObj.quality = newQuality + showObj.archive_firstmatch = archive_firstmatch + + # reversed for now + if bool(showObj.flatten_folders) != bool(flatten_folders): + showObj.flatten_folders = flatten_folders + try: + sickbeard.showQueueScheduler.action.refreshShow(showObj) # @UndefinedVariable + except exceptions.CantRefreshException, e: + errors.append('Unable to refresh this show: ' + ex(e)) + + showObj.paused = paused + showObj.scene = scene + showObj.anime = anime + showObj.sports = sports + showObj.subtitles = subtitles + showObj.air_by_date = air_by_date + + if not directCall: + showObj.lang = indexer_lang + showObj.dvdorder = dvdorder + showObj.rls_ignore_words = rls_ignore_words.strip() + showObj.rls_require_words = rls_require_words.strip() + + # if we change location clear the db of episodes, change it, write to db, and rescan + if os.path.normpath(showObj._location) != os.path.normpath(location): + logger.log(os.path.normpath(showObj._location) + ' != ' + os.path.normpath(location), logger.DEBUG) + if not ek.ek(os.path.isdir, location) and not sickbeard.CREATE_MISSING_SHOW_DIRS: + errors.append('New location %s does not exist' % location) + + # don't bother if we're going to update anyway + elif not do_update: + # change it + try: + showObj.location = location + try: + sickbeard.showQueueScheduler.action.refreshShow(showObj) # @UndefinedVariable + except exceptions.CantRefreshException, e: + errors.append('Unable to refresh this show:' + ex(e)) + # grab updated info from TVDB + # showObj.loadEpisodesFromIndexer() + # rescan the episodes in the new folder + except exceptions.NoNFOException: + errors.append( + "The folder at %s doesn't contain a tvshow.nfo - copy your files to that folder before you change the directory in SickGear." % location) + + # save it to the DB + showObj.saveToDB() + + # force the update + if do_update: + try: + sickbeard.showQueueScheduler.action.updateShow(showObj, True) # @UndefinedVariable + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) + except exceptions.CantUpdateException, e: + errors.append('Unable to force an update on the show.') + + if do_update_exceptions: + try: + scene_exceptions.update_scene_exceptions(showObj.indexerid, exceptions_list) # @UndefinedVdexerid) + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) + except exceptions.CantUpdateException, e: + errors.append('Unable to force an update on scene exceptions of the show.') + + if do_update_scene_numbering: + try: + sickbeard.scene_numbering.xem_refresh(showObj.indexerid, showObj.indexer) # @UndefinedVariable + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) + except exceptions.CantUpdateException, e: + errors.append('Unable to force an update on scene numbering of the show.') + + if directCall: + return errors + + if len(errors) > 0: + ui.notifications.error('%d error%s while saving changes:' % (len(errors), '' if len(errors) == 1 else 's'), + '
    ' + '\n'.join(['
  • %s
  • ' % error for error in errors]) + '
') + + self.redirect('/home/displayShow?show=' + show) + + def deleteShow(self, show=None, full=0): + + if show is None: + return self._genericMessage('Error', 'Invalid show ID') + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return self._genericMessage('Error', 'Unable to find the specified show') + + if sickbeard.showQueueScheduler.action.isBeingAdded( + showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable + return self._genericMessage("Error", "Shows can't be deleted while they're being added or updated.") + + if sickbeard.USE_TRAKT and sickbeard.TRAKT_SYNC: + # remove show from trakt.tv library + sickbeard.traktCheckerScheduler.action.removeShowFromTraktLibrary(showObj) + + showObj.deleteShow(bool(full)) + + ui.notifications.message('%s with %s' % (('Deleting', 'Trashing')[sickbeard.TRASH_REMOVE_SHOW], + ('media left untouched', 'all related media')[bool(full)]), + '%s' % showObj.name) + self.redirect('/home/') + + def refreshShow(self, show=None): + + if show is None: + return self._genericMessage('Error', 'Invalid show ID') + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return self._genericMessage('Error', 'Unable to find the specified show') + + # force the update from the DB + try: + sickbeard.showQueueScheduler.action.refreshShow(showObj) # @UndefinedVariable + except exceptions.CantRefreshException, e: + ui.notifications.error('Unable to refresh this show.', + ex(e)) + + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) + + self.redirect('/home/displayShow?show=' + str(showObj.indexerid)) + + def updateShow(self, show=None, force=0): + + if show is None: + return self._genericMessage('Error', 'Invalid show ID') + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return self._genericMessage('Error', 'Unable to find the specified show') + + # force the update + try: + sickbeard.showQueueScheduler.action.updateShow(showObj, bool(force)) # @UndefinedVariable + except exceptions.CantUpdateException, e: + ui.notifications.error('Unable to update this show.', + ex(e)) + + # just give it some time + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) + + self.redirect('/home/displayShow?show=' + str(showObj.indexerid)) + + def subtitleShow(self, show=None, force=0): + + if show is None: + return self._genericMessage('Error', 'Invalid show ID') + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return self._genericMessage('Error', 'Unable to find the specified show') + + # search and download subtitles + sickbeard.showQueueScheduler.action.downloadSubtitles(showObj, bool(force)) # @UndefinedVariable + + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) + + self.redirect('/home/displayShow?show=' + str(showObj.indexerid)) + + def updateXBMC(self, showName=None): + + # only send update to first host in the list -- workaround for xbmc sql backend users + if sickbeard.XBMC_UPDATE_ONLYFIRST: + # only send update to first host in the list -- workaround for xbmc sql backend users + host = sickbeard.XBMC_HOST.split(',')[0].strip() + else: + host = sickbeard.XBMC_HOST + + if notifiers.xbmc_notifier.update_library(showName=showName): + ui.notifications.message('Library update command sent to XBMC host(s): ' + host) + else: + ui.notifications.error('Unable to contact one or more XBMC host(s): ' + host) + self.redirect('/home/') + + def updatePLEX(self, *args, **kwargs): + result = notifiers.plex_notifier.update_library() + if None is result: + ui.notifications.message( + 'Library update command sent to', 'Plex Media Server host(s): ' + sickbeard.PLEX_SERVER_HOST.replace(',', ', ')) + else: + ui.notifications.error('Unable to contact', 'Plex Media Server host(s): ' + result.replace(',', ', ')) + self.redirect('/home/') + + def setStatus(self, show=None, eps=None, status=None, direct=False): + + if show is None or eps is None or status is None: + errMsg = 'You must specify a show and at least one episode' + if direct: + ui.notifications.error('Error', errMsg) + return json.dumps({'result': 'error'}) + else: + return self._genericMessage('Error', errMsg) + + if not statusStrings.has_key(int(status)): + errMsg = 'Invalid status' + if direct: + ui.notifications.error('Error', errMsg) + return json.dumps({'result': 'error'}) + else: + return self._genericMessage('Error', errMsg) + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + errMsg = 'Error', 'Show not in show list' + if direct: + ui.notifications.error('Error', errMsg) + return json.dumps({'result': 'error'}) + else: + return self._genericMessage('Error', errMsg) + + segments = {} + if eps is not None: + + sql_l = [] + for curEp in eps.split('|'): + + logger.log(u'Attempting to set status on episode ' + curEp + ' to ' + status, logger.DEBUG) + + epInfo = curEp.split('x') + + epObj = showObj.getEpisode(int(epInfo[0]), int(epInfo[1])) + + if epObj is None: + return self._genericMessage("Error", "Episode couldn't be retrieved") + + if int(status) in [WANTED, FAILED]: + # figure out what episodes are wanted so we can backlog them + if epObj.season in segments: + segments[epObj.season].append(epObj) + else: + segments[epObj.season] = [epObj] + + with epObj.lock: + # don't let them mess up UNAIRED episodes + if epObj.status == UNAIRED: + logger.log(u'Refusing to change status of ' + curEp + ' because it is UNAIRED', logger.ERROR) + continue + + if int( + status) in Quality.DOWNLOADED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED + [ + IGNORED] and not ek.ek(os.path.isfile, epObj.location): + logger.log( + u'Refusing to change status of ' + curEp + " to DOWNLOADED because it's not SNATCHED/DOWNLOADED", + logger.ERROR) + continue + + if int( + status) == FAILED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED: + logger.log( + u'Refusing to change status of ' + curEp + " to FAILED because it's not SNATCHED/DOWNLOADED", + logger.ERROR) + continue + + epObj.status = int(status) + + # mass add to database + result = epObj.get_sql() + if None is not result: + sql_l.append(result) + + if len(sql_l) > 0: + myDB = db.DBConnection() + myDB.mass_action(sql_l) + + if int(status) == WANTED: + msg = 'Backlog was automatically started for the following seasons of ' + showObj.name + ':
' + msg += '
    ' + + for season, segment in segments.items(): + cur_backlog_queue_item = search_queue.BacklogQueueItem(showObj, segment) + sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item) # @UndefinedVariable + + msg += '
  • Season ' + str(season) + '
  • ' + logger.log(u'Sending backlog for ' + showObj.name + ' season ' + str( + season) + ' because some eps were set to wanted') + + msg += '
' + + if segments: + ui.notifications.message('Backlog started', msg) + + if int(status) == FAILED: + msg = 'Retrying Search was automatically started for the following season of ' + showObj.name + ':
' + msg += '
    ' + + for season, segment in segments.items(): + cur_failed_queue_item = search_queue.FailedQueueItem(showObj, [segment]) + sickbeard.searchQueueScheduler.action.add_item(cur_failed_queue_item) # @UndefinedVariable + + msg += '
  • Season ' + str(season) + '
  • ' + logger.log(u'Retrying Search for ' + showObj.name + ' season ' + str( + season) + ' because some eps were set to failed') + + msg += '
' + + if segments: + ui.notifications.message('Retry Search started', msg) + + if direct: + return json.dumps({'result': 'success'}) + else: + self.redirect('/home/displayShow?show=' + show) + + def testRename(self, show=None): + + if show is None: + return self._genericMessage('Error', 'You must specify a show') + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return self._genericMessage('Error', 'Show not in show list') + + try: + show_loc = showObj.location # @UnusedVariable + except exceptions.ShowDirNotFoundException: + return self._genericMessage('Error', "Can't rename episodes when the show dir is missing.") + + ep_obj_rename_list = [] + + ep_obj_list = showObj.getAllEpisodes(has_location=True) + + for cur_ep_obj in ep_obj_list: + # Only want to rename if we have a location + if cur_ep_obj.location: + if cur_ep_obj.relatedEps: + # do we have one of multi-episodes in the rename list already + have_already = False + for cur_related_ep in cur_ep_obj.relatedEps + [cur_ep_obj]: + if cur_related_ep in ep_obj_rename_list: + have_already = True + break + if not have_already: + ep_obj_rename_list.append(cur_ep_obj) + else: + ep_obj_rename_list.append(cur_ep_obj) + + if ep_obj_rename_list: + # present season DESC episode DESC on screen + ep_obj_rename_list.reverse() + + t = PageTemplate(headers=self.request.headers, file='testRename.tmpl') + t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}] + t.ep_obj_list = ep_obj_rename_list + t.show = showObj + + return t.respond() + + def doRename(self, show=None, eps=None): + + if show is None or eps is None: + errMsg = 'You must specify a show and at least one episode' + return self._genericMessage('Error', errMsg) + + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if show_obj is None: + errMsg = 'Error', 'Show not in show list' + return self._genericMessage('Error', errMsg) + + try: + show_loc = show_obj.location # @UnusedVariable + except exceptions.ShowDirNotFoundException: + return self._genericMessage('Error', "Can't rename episodes when the show dir is missing.") + + if eps is None: + self.redirect('/home/displayShow?show=' + show) + + myDB = db.DBConnection() + for curEp in eps.split('|'): + + epInfo = curEp.split('x') + + # this is probably the worst possible way to deal with double eps but I've kinda painted myself into a corner here with this stupid database + ep_result = myDB.select( + 'SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND 5=5', + [show, epInfo[0], epInfo[1]]) + if not ep_result: + logger.log(u'Unable to find an episode for ' + curEp + ', skipping', logger.WARNING) + continue + related_eps_result = myDB.select('SELECT * FROM tv_episodes WHERE location = ? AND episode != ?', + [ep_result[0]['location'], epInfo[1]]) + + root_ep_obj = show_obj.getEpisode(int(epInfo[0]), int(epInfo[1])) + root_ep_obj.relatedEps = [] + + for cur_related_ep in related_eps_result: + related_ep_obj = show_obj.getEpisode(int(cur_related_ep['season']), int(cur_related_ep['episode'])) + if related_ep_obj not in root_ep_obj.relatedEps: + root_ep_obj.relatedEps.append(related_ep_obj) + + root_ep_obj.rename() + + self.redirect('/home/displayShow?show=' + show) + + def searchEpisode(self, show=None, season=None, episode=None): + + # retrieve the episode object and fail if we can't get one + ep_obj = self._getEpisode(show, season, episode) + if isinstance(ep_obj, str): + return json.dumps({'result': 'failure'}) + + # make a queue item for it and put it on the queue + ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj.show, ep_obj) + + sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) # @UndefinedVariable + + if ep_queue_item.success: + return returnManualSearchResult(ep_queue_item) + if not ep_queue_item.started and ep_queue_item.success is None: + return json.dumps({'result': 'success'}) #I Actually want to call it queued, because the search hasnt been started yet! + if ep_queue_item.started and ep_queue_item.success is None: + return json.dumps({'result': 'success'}) + else: + return json.dumps({'result': 'failure'}) + + ### Returns the current ep_queue_item status for the current viewed show. + # Possible status: Downloaded, Snatched, etc... + # Returns {'show': 279530, 'episodes' : ['episode' : 6, 'season' : 1, 'searchstatus' : 'queued', 'status' : 'running', 'quality': '4013'] + def getManualSearchStatus(self, show=None, season=None): + + episodes = [] + currentManualSearchThreadsQueued = [] + currentManualSearchThreadActive = [] + finishedManualSearchThreadItems= [] + + # Queued Searches + currentManualSearchThreadsQueued = sickbeard.searchQueueScheduler.action.get_all_ep_from_queue(show) + # Running Searches + if (sickbeard.searchQueueScheduler.action.is_manualsearch_in_progress()): + currentManualSearchThreadActive = sickbeard.searchQueueScheduler.action.currentItem + + # Finished Searches + finishedManualSearchThreadItems = sickbeard.search_queue.MANUAL_SEARCH_HISTORY + + if currentManualSearchThreadsQueued: + for searchThread in currentManualSearchThreadsQueued: + searchstatus = 'queued' + if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem): + episodes.append({'episode': searchThread.segment.episode, + 'episodeindexid': searchThread.segment.indexerid, + 'season' : searchThread.segment.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[searchThread.segment.status], + 'quality': self.getQualityClass(searchThread.segment)}) + else: + for epObj in searchThread.segment: + episodes.append({'episode': epObj.episode, + 'episodeindexid': epObj.indexerid, + 'season' : epObj.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[epObj.status], + 'quality': self.getQualityClass(epObj)}) + + if currentManualSearchThreadActive: + searchThread = currentManualSearchThreadActive + searchstatus = 'searching' + if searchThread.success: + searchstatus = 'finished' + else: + searchstatus = 'searching' + episodes.append({'episode': searchThread.segment.episode, + 'episodeindexid': searchThread.segment.indexerid, + 'season' : searchThread.segment.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[searchThread.segment.status], + 'quality': self.getQualityClass(searchThread.segment)}) + + if finishedManualSearchThreadItems: + for searchThread in finishedManualSearchThreadItems: + if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem): + if str(searchThread.show.indexerid) == show and not [x for x in episodes if x['episodeindexid'] == searchThread.segment.indexerid]: + searchstatus = 'finished' + episodes.append({'episode': searchThread.segment.episode, + 'episodeindexid': searchThread.segment.indexerid, + 'season' : searchThread.segment.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[searchThread.segment.status], + 'quality': self.getQualityClass(searchThread.segment)}) + else: + ### These are only Failed Downloads/Retry SearchThreadItems.. lets loop through the segement/episodes + if str(searchThread.show.indexerid) == show: + for epObj in searchThread.segment: + if not [x for x in episodes if x['episodeindexid'] == epObj.indexerid]: + searchstatus = 'finished' + episodes.append({'episode': epObj.episode, + 'episodeindexid': epObj.indexerid, + 'season' : epObj.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[epObj.status], + 'quality': self.getQualityClass(epObj)}) + + return json.dumps({'show': show, 'episodes' : episodes}) + + #return json.dumps() + + def getQualityClass(self, ep_obj): + # return the correct json value + + # Find the quality class for the episode + quality_class = Quality.qualityStrings[Quality.UNKNOWN] + ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status) + for x in (SD, HD720p, HD1080p): + if ep_quality in Quality.splitQuality(x)[0]: + quality_class = qualityPresetStrings[x] + break + + return quality_class + + def searchEpisodeSubtitles(self, show=None, season=None, episode=None): + # retrieve the episode object and fail if we can't get one + ep_obj = self._getEpisode(show, season, episode) + if isinstance(ep_obj, str): + return json.dumps({'result': 'failure'}) + + # try do download subtitles for that episode + previous_subtitles = set(subliminal.language.Language(x) for x in ep_obj.subtitles) + try: + ep_obj.subtitles = set(x.language for x in ep_obj.downloadSubtitles().values()[0]) + except: + return json.dumps({'result': 'failure'}) + + # return the correct json value + if previous_subtitles != ep_obj.subtitles: + status = 'New subtitles downloaded: %s' % ' '.join([ + "" + x.name + "" for x in + sorted(list(ep_obj.subtitles.difference(previous_subtitles)))]) + else: + status = 'No subtitles downloaded' + ui.notifications.message('Subtitles Search', status) + return json.dumps({'result': status, 'subtitles': ','.join(sorted([x.alpha2 for x in + ep_obj.subtitles.union(previous_subtitles)]))}) + + def setSceneNumbering(self, show, indexer, forSeason=None, forEpisode=None, forAbsolute=None, sceneSeason=None, + sceneEpisode=None, sceneAbsolute=None): + + # sanitize: + if forSeason in ['null', '']: forSeason = None + if forEpisode in ['null', '']: forEpisode = None + if forAbsolute in ['null', '']: forAbsolute = None + if sceneSeason in ['null', '']: sceneSeason = None + if sceneEpisode in ['null', '']: sceneEpisode = None + if sceneAbsolute in ['null', '']: sceneAbsolute = None + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj.is_anime: + result = { + 'success': True, + 'forAbsolute': forAbsolute, + } + else: + result = { + 'success': True, + 'forSeason': forSeason, + 'forEpisode': forEpisode, + } + + # retrieve the episode object and fail if we can't get one + if showObj.is_anime: + ep_obj = self._getEpisode(show, absolute=forAbsolute) + else: + ep_obj = self._getEpisode(show, forSeason, forEpisode) + + if isinstance(ep_obj, str): + result['success'] = False + result['errorMessage'] = ep_obj + elif showObj.is_anime: + logger.log(u'setAbsoluteSceneNumbering for %s from %s to %s' % + (show, forAbsolute, sceneAbsolute), logger.DEBUG) + + show = int(show) + indexer = int(indexer) + forAbsolute = int(forAbsolute) + if sceneAbsolute is not None: sceneAbsolute = int(sceneAbsolute) + + set_scene_numbering(show, indexer, absolute_number=forAbsolute, sceneAbsolute=sceneAbsolute) + else: + logger.log(u'setEpisodeSceneNumbering for %s from %sx%s to %sx%s' % + (show, forSeason, forEpisode, sceneSeason, sceneEpisode), logger.DEBUG) + + show = int(show) + indexer = int(indexer) + forSeason = int(forSeason) + forEpisode = int(forEpisode) + if sceneSeason is not None: sceneSeason = int(sceneSeason) + if sceneEpisode is not None: sceneEpisode = int(sceneEpisode) + + set_scene_numbering(show, indexer, season=forSeason, episode=forEpisode, sceneSeason=sceneSeason, + sceneEpisode=sceneEpisode) + + if showObj.is_anime: + sn = get_scene_absolute_numbering(show, indexer, forAbsolute) + if sn: + result['sceneAbsolute'] = sn + else: + result['sceneAbsolute'] = None + else: + sn = get_scene_numbering(show, indexer, forSeason, forEpisode) + if sn: + (result['sceneSeason'], result['sceneEpisode']) = sn + else: + (result['sceneSeason'], result['sceneEpisode']) = (None, None) + + return json.dumps(result) + + def retryEpisode(self, show, season, episode): + + # retrieve the episode object and fail if we can't get one + ep_obj = self._getEpisode(show, season, episode) + if isinstance(ep_obj, str): + return json.dumps({'result': 'failure'}) + + # make a queue item for it and put it on the queue + ep_queue_item = search_queue.FailedQueueItem(ep_obj.show, [ep_obj]) + sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) # @UndefinedVariable + + if ep_queue_item.success: + return returnManualSearchResult(ep_queue_item) + if not ep_queue_item.started and ep_queue_item.success is None: + return json.dumps({'result': 'success'}) #I Actually want to call it queued, because the search hasnt been started yet! + if ep_queue_item.started and ep_queue_item.success is None: + return json.dumps({'result': 'success'}) + else: + return json.dumps({'result': 'failure'}) + + @staticmethod + def fetch_releasegroups(show_name): + + if helpers.set_up_anidb_connection(): + try: + anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=show_name) + groups = anime.get_groups() + except Exception, e: + logger.log(u'exception msg: ' + str(e), logger.DEBUG) + return json.dumps({'result': 'fail', 'resp': 'connect'}) + + return json.dumps({'result': 'success', 'groups': groups}) + + return json.dumps({'result': 'fail', 'resp': 'init'}) + + +class HomePostProcess(Home): + def index(self, *args, **kwargs): + + t = PageTemplate(headers=self.request.headers, file='home_postprocess.tmpl') + t.submenu = self.HomeMenu() + return t.respond() + + def processEpisode(self, dir=None, nzbName=None, jobName=None, quiet=None, process_method=None, force=None, + is_priority=None, failed='0', type='auto', *args, **kwargs): + + if failed == '0': + failed = False + else: + failed = True + + if force in ['on', '1']: + force = True + else: + force = False + + if is_priority in ['on', '1']: + is_priority = True + else: + is_priority = False + + if not dir: + self.redirect('/home/postprocess/') + else: + result = processTV.processDir(dir, nzbName, process_method=process_method, force=force, + is_priority=is_priority, failed=failed, type=type) + if quiet is not None and int(quiet) == 1: + return result + + result = result.replace('\n', '
\n') + return self._genericMessage('Postprocessing results', result) + + +class NewHomeAddShows(Home): + def index(self, *args, **kwargs): + + t = PageTemplate(headers=self.request.headers, file='home_addShows.tmpl') + t.submenu = self.HomeMenu() + return t.respond() + + def getIndexerLanguages(self, *args, **kwargs): + result = sickbeard.indexerApi().config['valid_languages'] + + # Make sure list is sorted alphabetically but 'en' is in front + if 'en' in result: + del result[result.index('en')] + result.sort() + result.insert(0, 'en') + + return json.dumps({'results': result}) + + def sanitizeFileName(self, name): + return helpers.sanitizeFileName(name) + + def searchIndexersForShowName(self, search_term, lang='en', indexer=None): + if not lang or lang == 'null': + lang = 'en' + + search_term = search_term.encode('utf-8') + + results = {} + final_results = [] + + # Query Indexers for each search term and build the list of results + for indexer in sickbeard.indexerApi().indexers if not int(indexer) else [int(indexer)]: + lINDEXER_API_PARMS = sickbeard.indexerApi(indexer).api_params.copy() + lINDEXER_API_PARMS['language'] = lang + lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsListUI + t = sickbeard.indexerApi(indexer).indexer(**lINDEXER_API_PARMS) + + logger.log('Searching for Show with searchterm: %s on Indexer: %s' % ( + search_term, sickbeard.indexerApi(indexer).name), logger.DEBUG) + try: + # add search results + results.setdefault(indexer, []).extend(t[search_term]) + except Exception, e: + continue + + map(final_results.extend, + ([[sickbeard.indexerApi(id).name, id, sickbeard.indexerApi(id).config['show_url'], int(show['id']), + show['seriesname'], show['firstaired']] for show in shows] for id, shows in results.items())) + + lang_id = sickbeard.indexerApi().config['langabbv_to_id'][lang] + return json.dumps({'results': final_results, 'langid': lang_id}) + + def massAddTable(self, rootDir=None): + t = PageTemplate(headers=self.request.headers, file='home_massAddTable.tmpl') + t.submenu = self.HomeMenu() + + if not rootDir: + return 'No folders selected.' + elif type(rootDir) != list: + root_dirs = [rootDir] + else: + root_dirs = rootDir + + root_dirs = [urllib.unquote_plus(x) for x in root_dirs] + + if sickbeard.ROOT_DIRS: + default_index = int(sickbeard.ROOT_DIRS.split('|')[0]) + else: + default_index = 0 + + if len(root_dirs) > default_index: + tmp = root_dirs[default_index] + if tmp in root_dirs: + root_dirs.remove(tmp) + root_dirs = [tmp] + root_dirs + + dir_list = [] + + myDB = db.DBConnection() + for root_dir in root_dirs: + try: + file_list = ek.ek(os.listdir, root_dir) + except: + continue + + for cur_file in file_list: + + cur_path = ek.ek(os.path.normpath, ek.ek(os.path.join, root_dir, cur_file)) + if not ek.ek(os.path.isdir, cur_path): + continue + + cur_dir = { + 'dir': cur_path, + 'display_dir': '' + ek.ek(os.path.dirname, cur_path) + os.sep + '' + ek.ek( + os.path.basename, + cur_path), + } + + # see if the folder is in XBMC already + dirResults = myDB.select('SELECT * FROM tv_shows WHERE location = ?', [cur_path]) + + if dirResults: + cur_dir['added_already'] = True + else: + cur_dir['added_already'] = False + + dir_list.append(cur_dir) + + indexer_id = show_name = indexer = None + for cur_provider in sickbeard.metadata_provider_dict.values(): + if indexer_id and show_name: + continue + + (indexer_id, show_name, indexer) = cur_provider.retrieveShowMetadata(cur_path) + + # default to TVDB if indexer was not detected + if show_name and not (indexer or indexer_id): + (sn, idx, id) = helpers.searchIndexerForShowID(show_name, indexer, indexer_id) + + # set indexer and indexer_id from found info + if not indexer and idx: + indexer = idx + + if not indexer_id and id: + indexer_id = id + + cur_dir['existing_info'] = (indexer_id, show_name, indexer) + + if indexer_id and helpers.findCertainShow(sickbeard.showList, indexer_id): + cur_dir['added_already'] = True + + t.dirList = dir_list + + return t.respond() + + def newShow(self, show_to_add=None, other_shows=None, use_show_name=None): + """ + Display the new show page which collects a tvdb id, folder, and extra options and + posts them to addNewShow + """ + self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate') + self.set_header('Pragma', 'no-cache') + self.set_header('Expires', '0') + + t = PageTemplate(headers=self.request.headers, file='home_newShow.tmpl') + t.submenu = self.HomeMenu() + + indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add) + + if indexer_id and indexer and show_name: + use_provided_info = True + else: + use_provided_info = False + + # tell the template whether we're giving it show name & Indexer ID + t.use_provided_info = use_provided_info + + # use the given show_dir for the indexer search if available + if use_show_name: + t.default_show_name = show_name + elif not show_dir: + t.default_show_name = '' + elif not show_name: + t.default_show_name = ek.ek(os.path.basename, ek.ek(os.path.normpath, show_dir)).replace('.', ' ') + else: + t.default_show_name = show_name + + # carry a list of other dirs if given + if not other_shows: + other_shows = [] + elif type(other_shows) != list: + other_shows = [other_shows] + + if use_provided_info: + t.provided_indexer_id = int(indexer_id or 0) + t.provided_indexer_name = show_name + + t.provided_show_dir = show_dir + t.other_shows = other_shows + t.provided_indexer = int(indexer or sickbeard.INDEXER_DEFAULT) + t.indexers = sickbeard.indexerApi().indexers + t.whitelist = [] + t.blacklist = [] + t.groups = [] + + return t.respond() + + def recommendedShows(self, *args, **kwargs): + """ + Display the new show page which collects a tvdb id, folder, and extra options and + posts them to addNewShow + """ + self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate') + self.set_header('Pragma', 'no-cache') + self.set_header('Expires', '0') + + t = PageTemplate(headers=self.request.headers, file='home_recommendedShows.tmpl') + t.submenu = self.HomeMenu() + + return t.respond() + + def getRecommendedShows(self, *args, **kwargs): + final_results = [] + + logger.log(u'Getting recommended shows from Trakt.tv', logger.DEBUG) + recommendedlist = TraktCall('recommendations/shows.json/%API%', sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, + sickbeard.TRAKT_PASSWORD) + + if recommendedlist == 'NULL': + logger.log(u'No shows found in your recommendedlist, aborting recommendedlist update', logger.DEBUG) + return + + if recommendedlist is None: + logger.log(u'Could not connect to trakt service, aborting recommended list update', logger.ERROR) + return + + map(final_results.append, + ([show['url'], + show['title'], + show['overview'], + sbdatetime.sbdatetime.sbfdate(datetime.date.fromtimestamp(int(show['first_aired']))), + sickbeard.indexerApi(1).name, + sickbeard.indexerApi(1).config['icon'], + int(show['tvdb_id'] or 0), + '%s%s' % (sickbeard.indexerApi(1).config['show_url'], int(show['tvdb_id'] or 0)), + sickbeard.indexerApi(2).name, + sickbeard.indexerApi(2).config['icon'], + int(show['tvrage_id'] or 0), + '%s%s' % (sickbeard.indexerApi(2).config['show_url'], int(show['tvrage_id'] or 0)) + ] for show in recommendedlist if not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id'])))) + + self.set_header('Content-Type', 'application/json') + return json.dumps({'results': final_results}) + + def addRecommendedShow(self, whichSeries=None, indexerLang='en', rootDir=None, defaultStatus=None, + anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None, + fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None, + scene=None): + + indexer = 1 + indexer_name = sickbeard.indexerApi(int(indexer)).name + show_url = whichSeries.split('|')[1] + indexer_id = whichSeries.split('|')[0] + show_name = whichSeries.split('|')[2] + + return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, '']), + indexerLang, rootDir, + defaultStatus, + anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows, + skipShow, providedIndexer, anime, scene) + + def trendingShows(self, *args, **kwargs): + """ + Display the new show page which collects a tvdb id, folder, and extra options and + posts them to addNewShow + """ + t = PageTemplate(headers=self.request.headers, file='home_trendingShows.tmpl') + t.submenu = self.HomeMenu() + + t.trending_shows = TraktCall('shows/trending.json/%API%', sickbeard.TRAKT_API_KEY) + t.trending_inlibrary = 0 + if None is not t.trending_shows: + for item in t.trending_shows: + tvdbs = ['tvdb_id', 'tvrage_id'] + for index, tvdb in enumerate(tvdbs): + try: + item[u'show_id'] = str(item[tvdb]) + tvshow = helpers.findCertainShow(sickbeard.showList, int(item[tvdb])) + except: + continue + # check tvshow indexer is not using the same id from another indexer + if tvshow and (index + 1) == tvshow.indexer: + item[u'show_id'] = u'%s:%s' % (tvshow.indexer, item[tvdb]) + t.trending_inlibrary += 1 + break + + return t.respond() + + def existingShows(self, *args, **kwargs): + """ + Prints out the page to add existing shows from a root dir + """ + t = PageTemplate(headers=self.request.headers, file='home_addExistingShow.tmpl') + t.submenu = self.HomeMenu() + t.whitelist = [] + t.blacklist = [] + t.groups = [] + + return t.respond() + + def addTraktShow(self, indexer_id, showName): + if helpers.findCertainShow(sickbeard.showList, int(indexer_id)): + return + return self.newShow('|'.join(['', '', indexer_id, showName]), use_show_name=True) + + def addNewShow(self, whichSeries=None, indexerLang='en', rootDir=None, defaultStatus=None, + anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None, + fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None, + scene=None, blacklist=None, whitelist=None): + """ + Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are + provided then it forwards back to newShow, if not it goes to /home. + """ + + # grab our list of other dirs if given + if not other_shows: + other_shows = [] + elif type(other_shows) != list: + other_shows = [other_shows] + + def finishAddShow(): + # if there are no extra shows then go home + if not other_shows: + self.redirect('/home/') + + # peel off the next one + next_show_dir = other_shows[0] + rest_of_show_dirs = other_shows[1:] + + # go to add the next show + return self.newShow(next_show_dir, rest_of_show_dirs) + + # if we're skipping then behave accordingly + if skipShow: + return finishAddShow() + + # sanity check on our inputs + if (not rootDir and not fullShowPath) or not whichSeries: + return 'Missing params, no Indexer ID or folder:' + repr(whichSeries) + ' and ' + repr( + rootDir) + '/' + repr(fullShowPath) + + # figure out what show we're adding and where + series_pieces = whichSeries.split('|') + if (whichSeries and rootDir) or (whichSeries and fullShowPath and len(series_pieces) > 1): + if len(series_pieces) < 6: + logger.log('Unable to add show due to show selection. Not enough arguments: %s' % (repr(series_pieces)), + logger.ERROR) + ui.notifications.error('Unknown error. Unable to add show due to problem with show selection.') + self.redirect('/home/addShows/existingShows/') + indexer = int(series_pieces[1]) + indexer_id = int(series_pieces[3]) + show_name = series_pieces[4] + else: + # if no indexer was provided use the default indexer set in General settings + if not providedIndexer: + providedIndexer = sickbeard.INDEXER_DEFAULT + + indexer = int(providedIndexer) + indexer_id = int(whichSeries) + show_name = os.path.basename(os.path.normpath(fullShowPath)) + + # use the whole path if it's given, or else append the show name to the root dir to get the full show path + if fullShowPath: + show_dir = ek.ek(os.path.normpath, fullShowPath) + else: + show_dir = ek.ek(os.path.join, rootDir, helpers.sanitizeFileName(show_name)) + + # blanket policy - if the dir exists you should have used 'add existing show' numbnuts + if ek.ek(os.path.isdir, show_dir) and not fullShowPath: + ui.notifications.error('Unable to add show', 'Folder ' + show_dir + ' exists already') + self.redirect('/home/addShows/existingShows/') + + # don't create show dir if config says not to + if sickbeard.ADD_SHOWS_WO_DIR: + logger.log(u'Skipping initial creation of ' + show_dir + ' due to config.ini setting') + else: + dir_exists = helpers.makeDir(show_dir) + if not dir_exists: + logger.log(u'Unable to create the folder ' + show_dir + ", can't add the show", logger.ERROR) + ui.notifications.error('Unable to add show', + 'Unable to create the folder ' + show_dir + ", can't add the show") + self.redirect('/home/') + else: + helpers.chmodAsParent(show_dir) + + # prepare the inputs for passing along + scene = config.checkbox_to_value(scene) + anime = config.checkbox_to_value(anime) + flatten_folders = config.checkbox_to_value(flatten_folders) + subtitles = config.checkbox_to_value(subtitles) + + if whitelist: + whitelist = short_group_names(whitelist) + if blacklist: + blacklist = short_group_names(blacklist) + + if not anyQualities: + anyQualities = [] + if not bestQualities: + bestQualities = [] + if type(anyQualities) != list: + anyQualities = [anyQualities] + if type(bestQualities) != list: + bestQualities = [bestQualities] + newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities)) + + # add the show + sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality, + flatten_folders, indexerLang, subtitles, anime, + scene, None, blacklist, whitelist) # @UndefinedVariable + ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir) + + return finishAddShow() + + def split_extra_show(self, extra_show): + if not extra_show: + return (None, None, None, None) + split_vals = extra_show.split('|') + if len(split_vals) < 4: + indexer = split_vals[0] + show_dir = split_vals[1] + return (indexer, show_dir, None, None) + indexer = split_vals[0] + show_dir = split_vals[1] + indexer_id = split_vals[2] + show_name = '|'.join(split_vals[3:]) + + return (indexer, show_dir, indexer_id, show_name) + + def addExistingShows(self, shows_to_add=None, promptForSettings=None): + """ + Receives a dir list and add them. Adds the ones with given TVDB IDs first, then forwards + along to the newShow page. + """ + + # grab a list of other shows to add, if provided + if not shows_to_add: + shows_to_add = [] + elif type(shows_to_add) != list: + shows_to_add = [shows_to_add] + + shows_to_add = [urllib.unquote_plus(x) for x in shows_to_add] + + promptForSettings = config.checkbox_to_value(promptForSettings) + + indexer_id_given = [] + dirs_only = [] + # separate all the ones with Indexer IDs + for cur_dir in shows_to_add: + if '|' in cur_dir: + split_vals = cur_dir.split('|') + if len(split_vals) < 3: + dirs_only.append(cur_dir) + if not '|' in cur_dir: + dirs_only.append(cur_dir) + else: + indexer, show_dir, indexer_id, show_name = self.split_extra_show(cur_dir) + + if not show_dir or not indexer_id or not show_name: + continue + + indexer_id_given.append((int(indexer), show_dir, int(indexer_id), show_name)) + + + # if they want me to prompt for settings then I will just carry on to the newShow page + if promptForSettings and shows_to_add: + return self.newShow(shows_to_add[0], shows_to_add[1:]) + + # if they don't want me to prompt for settings then I can just add all the nfo shows now + num_added = 0 + for cur_show in indexer_id_given: + indexer, show_dir, indexer_id, show_name = cur_show + + if indexer is not None and indexer_id is not None: + # add the show + sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, + default_status=sickbeard.STATUS_DEFAULT, + quality=sickbeard.QUALITY_DEFAULT, + flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT, + subtitles=sickbeard.SUBTITLES_DEFAULT, + anime=sickbeard.ANIME_DEFAULT, + scene=sickbeard.SCENE_DEFAULT) + num_added += 1 + + if num_added: + ui.notifications.message('Shows Added', + 'Automatically added ' + str(num_added) + ' from their existing metadata files') + + # if we're done then go home + if not dirs_only: + self.redirect('/home/') + + # for the remaining shows we need to prompt for each one, so forward this on to the newShow page + return self.newShow(dirs_only[0], dirs_only[1:]) class Manage(MainHandler): + def ManageMenu(self): + manageMenu = [ + {'title': 'Backlog Overview', 'path': 'manage/backlogOverview/'}, + {'title': 'Manage Searches', 'path': 'manage/manageSearches/'}, + {'title': 'Episode Status Management', 'path': 'manage/episodeStatuses/'}, ] + + if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \ + and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https' + or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'): + manageMenu.append({'title': 'Manage Torrents', 'path': 'manage/manageTorrents/'}) + + if sickbeard.USE_SUBTITLES: + manageMenu.append({'title': 'Missed Subtitle Management', 'path': 'manage/subtitleMissed/'}) + + if sickbeard.USE_FAILED_DOWNLOADS: + manageMenu.append({'title': 'Failed Downloads', 'path': 'manage/failedDownloads/'}) + + return manageMenu + def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="manage.tmpl") - t.submenu = ManageMenu() - return _munge(t) + t = PageTemplate(headers=self.request.headers, file='manage.tmpl') + t.submenu = self.ManageMenu() + return t.respond() def showEpisodeStatuses(self, indexer_id, whichStatus): status_list = [int(whichStatus)] @@ -681,18 +2358,18 @@ class Manage(MainHandler): myDB = db.DBConnection() cur_show_results = myDB.select( - "SELECT season, episode, name FROM tv_episodes WHERE showid = ? AND season != 0 AND status IN (" + ','.join( - ['?'] * len(status_list)) + ")", [int(indexer_id)] + status_list) + 'SELECT season, episode, name FROM tv_episodes WHERE showid = ? AND season != 0 AND status IN (' + ','.join( + ['?'] * len(status_list)) + ')', [int(indexer_id)] + status_list) result = {} for cur_result in cur_show_results: - cur_season = int(cur_result["season"]) - cur_episode = int(cur_result["episode"]) + cur_season = int(cur_result['season']) + cur_episode = int(cur_result['episode']) if cur_season not in result: result[cur_season] = {} - result[cur_season][cur_episode] = cur_result["name"] + result[cur_season][cur_episode] = cur_result['name'] return json.dumps(result) @@ -706,39 +2383,39 @@ class Manage(MainHandler): else: status_list = [] - t = PageTemplate(headers=self.request.headers, file="manage_episodeStatuses.tmpl") - t.submenu = ManageMenu() + t = PageTemplate(headers=self.request.headers, file='manage_episodeStatuses.tmpl') + t.submenu = self.ManageMenu() t.whichStatus = whichStatus # if we have no status then this is as far as we need to go if not status_list: - return _munge(t) + return t.respond() myDB = db.DBConnection() status_results = myDB.select( - "SELECT show_name, tv_shows.indexer_id as indexer_id FROM tv_episodes, tv_shows WHERE tv_episodes.status IN (" + ','.join( + 'SELECT show_name, tv_shows.indexer_id as indexer_id FROM tv_episodes, tv_shows WHERE tv_episodes.status IN (' + ','.join( ['?'] * len( - status_list)) + ") AND season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name", + status_list)) + ') AND season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name', status_list) ep_counts = {} show_names = {} sorted_show_ids = [] for cur_status_result in status_results: - cur_indexer_id = int(cur_status_result["indexer_id"]) + cur_indexer_id = int(cur_status_result['indexer_id']) if cur_indexer_id not in ep_counts: ep_counts[cur_indexer_id] = 1 else: ep_counts[cur_indexer_id] += 1 - show_names[cur_indexer_id] = cur_status_result["show_name"] + show_names[cur_indexer_id] = cur_status_result['show_name'] if cur_indexer_id not in sorted_show_ids: sorted_show_ids.append(cur_indexer_id) t.show_names = show_names t.ep_counts = ep_counts t.sorted_show_ids = sorted_show_ids - return _munge(t) + return t.respond() def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs): @@ -764,19 +2441,19 @@ class Manage(MainHandler): myDB = db.DBConnection() for cur_indexer_id in to_change: - # get a list of all the eps we want to change if they just said "all" + # get a list of all the eps we want to change if they just said 'all' if 'all' in to_change[cur_indexer_id]: all_eps_results = myDB.select( - "SELECT season, episode FROM tv_episodes WHERE status IN (" + ','.join( - ['?'] * len(status_list)) + ") AND season != 0 AND showid = ?", + 'SELECT season, episode FROM tv_episodes WHERE status IN (' + ','.join( + ['?'] * len(status_list)) + ') AND season != 0 AND showid = ?', status_list + [cur_indexer_id]) - all_eps = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results] + all_eps = [str(x['season']) + 'x' + str(x['episode']) for x in all_eps_results] to_change[cur_indexer_id] = all_eps Home(self.application, self.request).setStatus(cur_indexer_id, '|'.join(to_change[cur_indexer_id]), newStatus, direct=True) - redirect('/manage/episodeStatuses/') + self.redirect('/manage/episodeStatuses/') def showSubtitleMissed(self, indexer_id, whichSubs): myDB = db.DBConnection() @@ -787,14 +2464,14 @@ class Manage(MainHandler): result = {} for cur_result in cur_show_results: if whichSubs == 'all': - if len(set(cur_result["subtitles"].split(',')).intersection(set(subtitles.wantedLanguages()))) >= len( + if len(set(cur_result['subtitles'].split(',')).intersection(set(subtitles.wantedLanguages()))) >= len( subtitles.wantedLanguages()): continue - elif whichSubs in cur_result["subtitles"].split(','): + elif whichSubs in cur_result['subtitles'].split(','): continue - cur_season = int(cur_result["season"]) - cur_episode = int(cur_result["episode"]) + cur_season = int(cur_result['season']) + cur_episode = int(cur_result['episode']) if cur_season not in result: result[cur_season] = {} @@ -802,22 +2479,22 @@ class Manage(MainHandler): if cur_episode not in result[cur_season]: result[cur_season][cur_episode] = {} - result[cur_season][cur_episode]["name"] = cur_result["name"] + result[cur_season][cur_episode]['name'] = cur_result['name'] - result[cur_season][cur_episode]["subtitles"] = ",".join( - subliminal.language.Language(subtitle).alpha2 for subtitle in cur_result["subtitles"].split(',')) if not \ - cur_result["subtitles"] == '' else '' + result[cur_season][cur_episode]['subtitles'] = ','.join( + subliminal.language.Language(subtitle).alpha2 for subtitle in cur_result['subtitles'].split(',')) if not \ + cur_result['subtitles'] == '' else '' return json.dumps(result) def subtitleMissed(self, whichSubs=None): - t = PageTemplate(headers=self.request.headers, file="manage_subtitleMissed.tmpl") - t.submenu = ManageMenu() + t = PageTemplate(headers=self.request.headers, file='manage_subtitleMissed.tmpl') + t.submenu = self.ManageMenu() t.whichSubs = whichSubs if not whichSubs: - return _munge(t) + return t.respond() myDB = db.DBConnection() status_results = myDB.select( @@ -828,26 +2505,26 @@ class Manage(MainHandler): sorted_show_ids = [] for cur_status_result in status_results: if whichSubs == 'all': - if len(set(cur_status_result["subtitles"].split(',')).intersection( + if len(set(cur_status_result['subtitles'].split(',')).intersection( set(subtitles.wantedLanguages()))) >= len(subtitles.wantedLanguages()): continue - elif whichSubs in cur_status_result["subtitles"].split(','): + elif whichSubs in cur_status_result['subtitles'].split(','): continue - cur_indexer_id = int(cur_status_result["indexer_id"]) + cur_indexer_id = int(cur_status_result['indexer_id']) if cur_indexer_id not in ep_counts: ep_counts[cur_indexer_id] = 1 else: ep_counts[cur_indexer_id] += 1 - show_names[cur_indexer_id] = cur_status_result["show_name"] + show_names[cur_indexer_id] = cur_status_result['show_name'] if cur_indexer_id not in sorted_show_ids: sorted_show_ids.append(cur_indexer_id) t.show_names = show_names t.ep_counts = ep_counts t.sorted_show_ids = sorted_show_ids - return _munge(t) + return t.respond() def downloadSubtitleMissed(self, *args, **kwargs): @@ -867,13 +2544,13 @@ class Manage(MainHandler): to_download[indexer_id].append(what) for cur_indexer_id in to_download: - # get a list of all the eps we want to download subtitles if they just said "all" + # get a list of all the eps we want to download subtitles if they just said 'all' if 'all' in to_download[cur_indexer_id]: myDB = db.DBConnection() all_eps_results = myDB.select( "SELECT season, episode FROM tv_episodes WHERE status LIKE '%4' AND season != 0 AND showid = ?", [cur_indexer_id]) - to_download[cur_indexer_id] = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results] + to_download[cur_indexer_id] = [str(x['season']) + 'x' + str(x['episode']) for x in all_eps_results] for epResult in to_download[cur_indexer_id]: season, episode = epResult.split('x') @@ -881,7 +2558,7 @@ class Manage(MainHandler): show = sickbeard.helpers.findCertainShow(sickbeard.showList, int(cur_indexer_id)) subtitles = show.getEpisode(int(season), int(episode)).downloadSubtitles() - redirect('/manage/subtitleMissed/') + self.redirect('/manage/subtitleMissed/') def backlogShow(self, indexer_id): @@ -890,12 +2567,12 @@ class Manage(MainHandler): if show_obj: sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj]) # @UndefinedVariable - redirect("/manage/backlogOverview/") + self.redirect('/manage/backlogOverview/') def backlogOverview(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="manage_backlogOverview.tmpl") - t.submenu = ManageMenu() + t = PageTemplate(headers=self.request.headers, file='manage_backlogOverview.tmpl') + t.submenu = self.ManageMenu() showCounts = {} showCats = {} @@ -914,13 +2591,13 @@ class Manage(MainHandler): epCounts[Overview.SNATCHED] = 0 sqlResults = myDB.select( - "SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC", + 'SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC', [curShow.indexerid]) for curResult in sqlResults: - curEpCat = curShow.getOverview(int(curResult["status"])) + curEpCat = curShow.getOverview(int(curResult['status'])) if curEpCat: - epCats[str(curResult["season"]) + "x" + str(curResult["episode"])] = curEpCat + epCats[str(curResult['season']) + 'x' + str(curResult['episode'])] = curEpCat epCounts[curEpCat] += 1 showCounts[curShow.indexerid] = epCounts @@ -931,17 +2608,17 @@ class Manage(MainHandler): t.showCats = showCats t.showSQLResults = showSQLResults - return _munge(t) + return t.respond() def massEdit(self, toEdit=None): - t = PageTemplate(headers=self.request.headers, file="manage_massEdit.tmpl") - t.submenu = ManageMenu() + t = PageTemplate(headers=self.request.headers, file='manage_massEdit.tmpl') + t.submenu = self.ManageMenu() if not toEdit: - redirect("/manage/") + self.redirect('/manage/') - showIDs = toEdit.split("|") + showIDs = toEdit.split('|') showList = [] for curID in showIDs: curID = int(curID) @@ -1054,7 +2731,7 @@ class Manage(MainHandler): t.air_by_date_value = last_air_by_date if air_by_date_all_same else None t.root_dir_list = root_dir_list - return _munge(t) + return t.respond() def massEditSubmit(self, archive_firstmatch=None, paused=None, anime=None, sports=None, scene=None, flatten_folders=None, quality_preset=False, @@ -1069,7 +2746,7 @@ class Manage(MainHandler): end_dir = kwargs['new_root_dir_' + which_index] dir_map[kwargs[cur_arg]] = end_dir - showIDs = toEdit.split("|") + showIDs = toEdit.split('|') errors = [] for curShow in showIDs: curErrors = [] @@ -1082,7 +2759,7 @@ class Manage(MainHandler): if cur_root_dir in dir_map and cur_root_dir != dir_map[cur_root_dir]: new_show_dir = ek.ek(os.path.join, dir_map[cur_root_dir], cur_show_dir) logger.log( - u"For show " + showObj.name + " changing dir from " + showObj._location + " to " + new_show_dir) + u'For show ' + showObj.name + ' changing dir from ' + showObj._location + ' to ' + new_show_dir) else: new_show_dir = showObj._location @@ -1150,15 +2827,15 @@ class Manage(MainHandler): directCall=True) if curErrors: - logger.log(u"Errors: " + str(curErrors), logger.ERROR) + logger.log(u'Errors: ' + str(curErrors), logger.ERROR) errors.append('%s:\n
    ' % showObj.name + ' '.join( - ['
  • %s
  • ' % error for error in curErrors]) + "
") + ['
  • %s
  • ' % error for error in curErrors]) + '') if len(errors) > 0: - ui.notifications.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"), - " ".join(errors)) + ui.notifications.error('%d error%s while saving changes:' % (len(errors), '' if len(errors) == 1 else 's'), + ' '.join(errors)) - redirect("/manage/") + self.redirect('/manage/') def massUpdate(self, toUpdate=None, toRefresh=None, toRename=None, toDelete=None, toRemove=None, toMetadata=None, toSubtitle=None): @@ -1228,7 +2905,7 @@ class Manage(MainHandler): sickbeard.showQueueScheduler.action.updateShow(showObj, True) # @UndefinedVariable updates.append(showObj.name) except exceptions.CantUpdateException, e: - errors.append("Unable to update show " + showObj.name + ": " + ex(e)) + errors.append('Unable to update show ' + showObj.name + ': ' + ex(e)) # don't bother refreshing shows that were updated anyway if curShowID in toRefresh and curShowID not in toUpdate: @@ -1236,7 +2913,7 @@ class Manage(MainHandler): sickbeard.showQueueScheduler.action.refreshShow(showObj) # @UndefinedVariable refreshes.append(showObj.name) except exceptions.CantRefreshException, e: - errors.append("Unable to refresh show " + showObj.name + ": " + ex(e)) + errors.append('Unable to refresh show ' + showObj.name + ': ' + ex(e)) if curShowID in toRename: sickbeard.showQueueScheduler.action.renameShowEpisodes(showObj) # @UndefinedVariable @@ -1247,42 +2924,42 @@ class Manage(MainHandler): subtitles.append(showObj.name) if len(errors) > 0: - ui.notifications.error("Errors encountered", + ui.notifications.error('Errors encountered', '
    \n'.join(errors)) - messageDetail = "" + messageDetail = '' if len(updates) > 0: - messageDetail += "
    Updates
    • " - messageDetail += "
    • ".join(updates) - messageDetail += "
    " + messageDetail += '
    Updates
    • ' + messageDetail += '
    • '.join(updates) + messageDetail += '
    ' if len(refreshes) > 0: - messageDetail += "
    Refreshes
    • " - messageDetail += "
    • ".join(refreshes) - messageDetail += "
    " + messageDetail += '
    Refreshes
    • ' + messageDetail += '
    • '.join(refreshes) + messageDetail += '
    ' if len(renames) > 0: - messageDetail += "
    Renames
    • " - messageDetail += "
    • ".join(renames) - messageDetail += "
    " + messageDetail += '
    Renames
    • ' + messageDetail += '
    • '.join(renames) + messageDetail += '
    ' if len(subtitles) > 0: - messageDetail += "
    Subtitles
    • " - messageDetail += "
    • ".join(subtitles) - messageDetail += "
    " + messageDetail += '
    Subtitles
    • ' + messageDetail += '
    • '.join(subtitles) + messageDetail += '
    ' if len(updates + refreshes + renames + subtitles) > 0: - ui.notifications.message("The following actions were queued:", + ui.notifications.message('The following actions were queued:', messageDetail) - redirect("/manage/") + self.redirect('/manage/') def manageTorrents(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="manage_torrents.tmpl") + t = PageTemplate(headers=self.request.headers, file='manage_torrents.tmpl') t.info_download_station = '' - t.submenu = ManageMenu() + t.submenu = self.ManageMenu() if re.search('localhost', sickbeard.TORRENT_HOST): @@ -1301,44 +2978,103 @@ class Manage(MainHandler): else: t.info_download_station = '

    To have a better experience please set the Download Station alias as download, you can check this setting in the Synology DSM Control Panel > Application Portal. Make sure you allow DSM to be embedded with iFrames too in Control Panel > DSM Settings > Security.


    There is more information about this available here.


    ' - return _munge(t) + return t.respond() def failedDownloads(self, limit=100, toRemove=None): myDB = db.DBConnection('failed.db') - if limit == "0": - sqlResults = myDB.select("SELECT * FROM failed") + if limit == '0': + sqlResults = myDB.select('SELECT * FROM failed') else: - sqlResults = myDB.select("SELECT * FROM failed LIMIT ?", [limit]) + sqlResults = myDB.select('SELECT * FROM failed LIMIT ?', [limit]) - toRemove = toRemove.split("|") if toRemove is not None else [] + toRemove = toRemove.split('|') if toRemove is not None else [] for release in toRemove: myDB.action('DELETE FROM failed WHERE release = ?', [release]) if toRemove: - redirect('/manage/failedDownloads/') + self.redirect('/manage/failedDownloads/') - t = PageTemplate(headers=self.request.headers, file="manage_failedDownloads.tmpl") + t = PageTemplate(headers=self.request.headers, file='manage_failedDownloads.tmpl') t.failedResults = sqlResults t.limit = limit - t.submenu = ManageMenu() + t.submenu = self.ManageMenu() - return _munge(t) + return t.respond() + + +class ManageSearches(Manage): + def index(self, *args, **kwargs): + t = PageTemplate(headers=self.request.headers, file='manage_manageSearches.tmpl') + # t.backlogPI = sickbeard.backlogSearchScheduler.action.getProgressIndicator() + t.backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused() # @UndefinedVariable + t.backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress() # @UndefinedVariable + t.recentSearchStatus = sickbeard.recentSearchScheduler.action.amActive # @UndefinedVariable + t.findPropersStatus = sickbeard.properFinderScheduler.action.amActive # @UndefinedVariable + t.queueLength = sickbeard.searchQueueScheduler.action.queue_length() + + t.submenu = self.ManageMenu() + + return t.respond() + + def forceVersionCheck(self, *args, **kwargs): + # force a check to see if there is a new version + if sickbeard.versionCheckScheduler.action.check_for_new_version(force=True): + logger.log(u'Forcing version check') + + self.redirect('/home/') + + def forceBacklog(self, *args, **kwargs): + # force it to run the next time it looks + result = sickbeard.backlogSearchScheduler.forceRun() + if result: + logger.log(u'Backlog search forced') + ui.notifications.message('Backlog search started') + + self.redirect('/manage/manageSearches/') + + def forceSearch(self, *args, **kwargs): + + # force it to run the next time it looks + result = sickbeard.recentSearchScheduler.forceRun() + if result: + logger.log(u'Recent search forced') + ui.notifications.message('Recent search started') + + self.redirect('/manage/manageSearches/') + + def forceFindPropers(self, *args, **kwargs): + + # force it to run the next time it looks + result = sickbeard.properFinderScheduler.forceRun() + if result: + logger.log(u'Find propers search forced') + ui.notifications.message('Find propers search started') + + self.redirect('/manage/manageSearches/') + + def pauseBacklog(self, paused=None): + if paused == '1': + sickbeard.searchQueueScheduler.action.pause_backlog() # @UndefinedVariable + else: + sickbeard.searchQueueScheduler.action.unpause_backlog() # @UndefinedVariable + + self.redirect('/manage/manageSearches/') class History(MainHandler): def index(self, limit=100): - # sqlResults = myDB.select("SELECT h.*, show_name, name FROM history h, tv_shows s, tv_episodes e WHERE h.showid=s.indexer_id AND h.showid=e.showid AND h.season=e.season AND h.episode=e.episode ORDER BY date DESC LIMIT "+str(numPerPage*(p-1))+", "+str(numPerPage)) + # sqlResults = myDB.select('SELECT h.*, show_name, name FROM history h, tv_shows s, tv_episodes e WHERE h.showid=s.indexer_id AND h.showid=e.showid AND h.season=e.season AND h.episode=e.episode ORDER BY date DESC LIMIT '+str(numPerPage*(p-1))+', '+str(numPerPage)) myDB = db.DBConnection() - if limit == "0": + if limit == '0': sqlResults = myDB.select( - "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC") + 'SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC') else: sqlResults = myDB.select( - "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC LIMIT ?", + 'SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC LIMIT ?', [limit]) history = {'show_id': 0, 'season': 0, 'episode': 0, 'quality': 0, @@ -1388,7 +3124,7 @@ class History(MainHandler): history['actions'].append(action) history['actions'].sort(key=lambda x: x['time'], reverse=True) - t = PageTemplate(headers=self.request.headers, file="history.tmpl") + t = PageTemplate(headers=self.request.headers, file='history.tmpl') t.historyResults = sqlResults t.compactResults = compact t.limit = limit @@ -1397,45 +3133,52 @@ class History(MainHandler): {'title': 'Trim History', 'path': 'history/trimHistory'}, ] - return _munge(t) - + return t.respond() def clearHistory(self, *args, **kwargs): myDB = db.DBConnection() - myDB.action("DELETE FROM history WHERE 1=1") + myDB.action('DELETE FROM history WHERE 1=1') ui.notifications.message('History cleared') - redirect("/history/") - + self.redirect('/history/') def trimHistory(self, *args, **kwargs): myDB = db.DBConnection() - myDB.action("DELETE FROM history WHERE date < " + str( + myDB.action('DELETE FROM history WHERE date < ' + str( (datetime.datetime.today() - datetime.timedelta(days=30)).strftime(history.dateFormat))) ui.notifications.message('Removed history entries greater than 30 days old') - redirect("/history/") + self.redirect('/history/') -ConfigMenu = [ - {'title': 'General', 'path': 'config/general/'}, - {'title': 'Search Settings', 'path': 'config/search/'}, - {'title': 'Search Providers', 'path': 'config/providers/'}, - {'title': 'Subtitles Settings', 'path': 'config/subtitles/'}, - {'title': 'Post Processing', 'path': 'config/postProcessing/'}, - {'title': 'Notifications', 'path': 'config/notifications/'}, - {'title': 'Anime', 'path': 'config/anime/'}, -] +class Config(MainHandler): + @staticmethod + def ConfigMenu(): + return [ + {'title': 'General', 'path': 'config/general/'}, + {'title': 'Search Settings', 'path': 'config/search/'}, + {'title': 'Search Providers', 'path': 'config/providers/'}, + {'title': 'Subtitles Settings', 'path': 'config/subtitles/'}, + {'title': 'Post Processing', 'path': 'config/postProcessing/'}, + {'title': 'Notifications', 'path': 'config/notifications/'}, + {'title': 'Anime', 'path': 'config/anime/'}, + ] + + def index(self, *args, **kwargs): + t = PageTemplate(headers=self.request.headers, file='config.tmpl') + t.submenu = self.ConfigMenu + + return t.respond() -class ConfigGeneral(MainHandler): +class ConfigGeneral(Config): def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config_general.tmpl") - t.submenu = ConfigMenu - return _munge(t) + t = PageTemplate(headers=self.request.headers, file='config_general.tmpl') + t.submenu = self.ConfigMenu + return t.respond() def saveRootDirs(self, rootDirString=None): sickbeard.ROOT_DIRS = rootDirString @@ -1486,7 +3229,7 @@ class ConfigGeneral(MainHandler): m.update(r) # Return a hex digest of the md5, eg 49f68a5c8493ec2c0bf489821c21fc3b - logger.log(u"New API generated") + logger.log(u'New API generated') return m.hexdigest() def saveGeneral(self, log_dir=None, web_port=None, web_log=None, encryption_version=None, web_ipv6=None, @@ -1546,12 +3289,12 @@ class ConfigGeneral(MainHandler): if time_preset: sickbeard.TIME_PRESET_W_SECONDS = time_preset - sickbeard.TIME_PRESET = sickbeard.TIME_PRESET_W_SECONDS.replace(u":%S", u"") + sickbeard.TIME_PRESET = sickbeard.TIME_PRESET_W_SECONDS.replace(u':%S', u'') sickbeard.TIMEZONE_DISPLAY = timezone_display if not config.change_LOG_DIR(log_dir, web_log): - results += ["Unable to create directory " + os.path.normpath(log_dir) + ", log directory not changed."] + results += ['Unable to create directory ' + os.path.normpath(log_dir) + ', log directory not changed.'] sickbeard.USE_API = config.checkbox_to_value(use_api) sickbeard.API_KEY = api_key @@ -1560,11 +3303,11 @@ class ConfigGeneral(MainHandler): if not config.change_HTTPS_CERT(https_cert): results += [ - "Unable to create directory " + os.path.normpath(https_cert) + ", https cert directory not changed."] + 'Unable to create directory ' + os.path.normpath(https_cert) + ', https cert directory not changed.'] if not config.change_HTTPS_KEY(https_key): results += [ - "Unable to create directory " + os.path.normpath(https_key) + ", https key directory not changed."] + 'Unable to create directory ' + os.path.normpath(https_key) + ', https key directory not changed.'] sickbeard.HANDLE_REVERSE_PROXY = config.checkbox_to_value(handle_reverse_proxy) @@ -1580,15 +3323,15 @@ class ConfigGeneral(MainHandler): else: ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE)) - redirect("/config/general/") + self.redirect('/config/general/') -class ConfigSearch(MainHandler): +class ConfigSearch(Config): def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config_search.tmpl") - t.submenu = ConfigMenu - return _munge(t) + t = PageTemplate(headers=self.request.headers, file='config_search.tmpl') + t.submenu = self.ConfigMenu + return t.respond() def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None, sab_apikey=None, sab_category=None, sab_host=None, nzbget_username=None, nzbget_password=None, @@ -1603,10 +3346,10 @@ class ConfigSearch(MainHandler): results = [] if not config.change_NZB_DIR(nzb_dir): - results += ["Unable to create directory " + os.path.normpath(nzb_dir) + ", dir not changed."] + results += ['Unable to create directory ' + os.path.normpath(nzb_dir) + ', dir not changed.'] if not config.change_TORRENT_DIR(torrent_dir): - results += ["Unable to create directory " + os.path.normpath(torrent_dir) + ", dir not changed."] + results += ['Unable to create directory ' + os.path.normpath(torrent_dir) + ', dir not changed.'] config.change_RECENTSEARCH_FREQUENCY(recentsearch_frequency) @@ -1620,8 +3363,8 @@ class ConfigSearch(MainHandler): sickbeard.TORRENT_METHOD = torrent_method sickbeard.USENET_RETENTION = config.to_int(usenet_retention, default=500) - sickbeard.IGNORE_WORDS = ignore_words if ignore_words else "" - sickbeard.REQUIRE_WORDS = require_words if require_words else "" + sickbeard.IGNORE_WORDS = ignore_words if ignore_words else '' + sickbeard.REQUIRE_WORDS = require_words if require_words else '' sickbeard.DOWNLOAD_PROPERS = config.checkbox_to_value(download_propers) sickbeard.CHECK_PROPERS_INTERVAL = check_propers_interval @@ -1661,15 +3404,15 @@ class ConfigSearch(MainHandler): else: ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE)) - redirect("/config/search/") + self.redirect('/config/search/') -class ConfigPostProcessing(MainHandler): +class ConfigPostProcessing(Config): def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config_postProcessing.tmpl") - t.submenu = ConfigMenu - return _munge(t) + t = PageTemplate(headers=self.request.headers, file='config_postProcessing.tmpl') + t.submenu = self.ConfigMenu + return t.respond() def savePostProcessing(self, naming_pattern=None, naming_multi_ep=None, xbmc_data=None, xbmc_12plus_data=None, mediabrowser_data=None, sony_ps3_data=None, @@ -1687,7 +3430,7 @@ class ConfigPostProcessing(MainHandler): results = [] if not config.change_TV_DOWNLOAD_DIR(tv_download_dir): - results += ["Unable to create directory " + os.path.normpath(tv_download_dir) + ", dir not changed."] + results += ['Unable to create directory ' + os.path.normpath(tv_download_dir) + ', dir not changed.'] sickbeard.PROCESS_AUTOMATICALLY = config.checkbox_to_value(process_automatically) config.change_AUTOPOSTPROCESSER_FREQUENCY(autopostprocesser_frequency) @@ -1702,7 +3445,7 @@ class ConfigPostProcessing(MainHandler): sickbeard.UNPACK = config.checkbox_to_value(unpack) else: sickbeard.UNPACK = 0 - results.append("Unpacking Not Supported, disabling unpack setting") + results.append('Unpacking Not Supported, disabling unpack setting') else: sickbeard.UNPACK = config.checkbox_to_value(unpack) @@ -1738,39 +3481,39 @@ class ConfigPostProcessing(MainHandler): sickbeard.metadata_provider_dict['TIVO'].set_config(sickbeard.METADATA_TIVO) sickbeard.metadata_provider_dict['Mede8er'].set_config(sickbeard.METADATA_MEDE8ER) - if self.isNamingValid(naming_pattern, naming_multi_ep, anime_type=naming_anime) != "invalid": + if self.isNamingValid(naming_pattern, naming_multi_ep, anime_type=naming_anime) != 'invalid': sickbeard.NAMING_PATTERN = naming_pattern sickbeard.NAMING_MULTI_EP = int(naming_multi_ep) sickbeard.NAMING_ANIME = int(naming_anime) sickbeard.NAMING_FORCE_FOLDERS = naming.check_force_season_folders() else: if int(naming_anime) in [1, 2]: - results.append("You tried saving an invalid anime naming config, not saving your naming settings") + results.append('You tried saving an invalid anime naming config, not saving your naming settings') else: - results.append("You tried saving an invalid naming config, not saving your naming settings") + results.append('You tried saving an invalid naming config, not saving your naming settings') - if self.isNamingValid(naming_anime_pattern, naming_anime_multi_ep, anime_type=naming_anime) != "invalid": + if self.isNamingValid(naming_anime_pattern, naming_anime_multi_ep, anime_type=naming_anime) != 'invalid': sickbeard.NAMING_ANIME_PATTERN = naming_anime_pattern sickbeard.NAMING_ANIME_MULTI_EP = int(naming_anime_multi_ep) sickbeard.NAMING_ANIME = int(naming_anime) sickbeard.NAMING_FORCE_FOLDERS = naming.check_force_season_folders() else: if int(naming_anime) in [1, 2]: - results.append("You tried saving an invalid anime naming config, not saving your naming settings") + results.append('You tried saving an invalid anime naming config, not saving your naming settings') else: - results.append("You tried saving an invalid naming config, not saving your naming settings") + results.append('You tried saving an invalid naming config, not saving your naming settings') - if self.isNamingValid(naming_abd_pattern, None, abd=True) != "invalid": + if self.isNamingValid(naming_abd_pattern, None, abd=True) != 'invalid': sickbeard.NAMING_ABD_PATTERN = naming_abd_pattern else: results.append( - "You tried saving an invalid air-by-date naming config, not saving your air-by-date settings") + 'You tried saving an invalid air-by-date naming config, not saving your air-by-date settings') - if self.isNamingValid(naming_sports_pattern, None, sports=True) != "invalid": + if self.isNamingValid(naming_sports_pattern, None, sports=True) != 'invalid': sickbeard.NAMING_SPORTS_PATTERN = naming_sports_pattern else: results.append( - "You tried saving an invalid sports naming config, not saving your sports settings") + 'You tried saving an invalid sports naming config, not saving your sports settings') sickbeard.save_config() @@ -1782,7 +3525,7 @@ class ConfigPostProcessing(MainHandler): else: ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE)) - redirect("/config/postProcessing/") + self.redirect('/config/postProcessing/') def testNaming(self, pattern=None, multi=None, abd=False, sports=False, anime_type=None): @@ -1800,7 +3543,7 @@ class ConfigPostProcessing(MainHandler): def isNamingValid(self, pattern=None, multi=None, abd=False, sports=False, anime_type=None): if pattern is None: - return "invalid" + return 'invalid' if multi is not None: multi = int(multi) @@ -1826,11 +3569,11 @@ class ConfigPostProcessing(MainHandler): require_season_folders = naming.check_force_season_folders(pattern, multi, anime_type) if is_valid and not require_season_folders: - return "valid" + return 'valid' elif is_valid and require_season_folders: - return "seasonfolders" + return 'seasonfolders' else: - return "invalid" + return 'invalid' def isRarSupported(self, *args, **kwargs): """ @@ -1850,11 +3593,11 @@ class ConfigPostProcessing(MainHandler): return 'not supported' -class ConfigProviders(MainHandler): +class ConfigProviders(Config): def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config_providers.tmpl") - t.submenu = ConfigMenu - return _munge(t) + t = PageTemplate(headers=self.request.headers, file='config_providers.tmpl') + t.submenu = self.ConfigMenu + return t.respond() def canAddNewznabProvider(self, name): @@ -1902,17 +3645,17 @@ class ConfigProviders(MainHandler): Using the default url/api?cat http://yournewznaburl.com/api?t=caps&apikey=yourapikey ''' - error = "" + error = '' success = False if not name: - error += "\nNo Provider Name specified" + error += '\nNo Provider Name specified' if not url: - error += "\nNo Provider Url specified" + error += '\nNo Provider Url specified' if not key: - error += "\nNo Provider Api key specified" + error += '\nNo Provider Api key specified' - if error <> "": + if error <> '': return json.dumps({'success' : False, 'error': error}) #Get list with Newznabproviders @@ -2279,15 +4022,14 @@ class ConfigProviders(MainHandler): else: ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE)) - redirect("/config/providers/") + self.redirect('/config/providers/') -class ConfigNotifications(MainHandler): +class ConfigNotifications(Config): def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config_notifications.tmpl") - t.submenu = ConfigMenu - return _munge(t) - + t = PageTemplate(headers=self.request.headers, file='config_notifications.tmpl') + t.submenu = self.ConfigMenu + return t.respond() def saveNotifications(self, use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None, @@ -2481,15 +4223,14 @@ class ConfigNotifications(MainHandler): else: ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE)) - redirect("/config/notifications/") + self.redirect('/config/notifications/') -class ConfigSubtitles(MainHandler): +class ConfigSubtitles(Config): def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config_subtitles.tmpl") - t.submenu = ConfigMenu - return _munge(t) - + t = PageTemplate(headers=self.request.headers, file='config_subtitles.tmpl') + t.submenu = self.ConfigMenu + return t.respond() def saveSubtitles(self, use_subtitles=None, subtitles_plugins=None, subtitles_languages=None, subtitles_dir=None, service_order=None, subtitles_history=None, subtitles_finder_frequency=None): @@ -2498,13 +4239,13 @@ class ConfigSubtitles(MainHandler): if subtitles_finder_frequency == '' or subtitles_finder_frequency is None: subtitles_finder_frequency = 1 - if use_subtitles == "on" and not sickbeard.subtitlesFinderScheduler.isAlive(): + if use_subtitles == 'on' and not sickbeard.subtitlesFinderScheduler.isAlive(): sickbeard.subtitlesFinderScheduler.silent = False sickbeard.subtitlesFinderScheduler.start() else: sickbeard.subtitlesFinderScheduler.stop.set() sickbeard.subtitlesFinderScheduler.silent = True - logger.log(u"Waiting for the SUBTITLESFINDER thread to exit") + logger.log(u'Waiting for the SUBTITLESFINDER thread to exit') try: sickbeard.subtitlesFinderScheduler.join(5) except: @@ -2539,15 +4280,15 @@ class ConfigSubtitles(MainHandler): else: ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE)) - redirect("/config/subtitles/") + self.redirect('/config/subtitles/') -class ConfigAnime(MainHandler): +class ConfigAnime(Config): def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config_anime.tmpl") - t.submenu = ConfigMenu - return _munge(t) + t = PageTemplate(headers=self.request.headers, file='config_anime.tmpl') + t.submenu = self.ConfigMenu + return t.respond() def saveAnime(self, use_anidb=None, anidb_username=None, anidb_password=None, anidb_use_mylist=None, split_home=None, anime_treat_as_hdtv=None): @@ -2571,599 +4312,48 @@ class ConfigAnime(MainHandler): else: ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE)) - redirect("/config/anime/") + self.redirect('/config/anime/') -class Config(MainHandler): - def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="config.tmpl") - t.submenu = ConfigMenu - return _munge(t) +class UI(MainHandler): + def add_message(self): + ui.notifications.message('Test 1', 'This is test number 1') + ui.notifications.error('Test 2', 'This is test number 2') - # map class names to urls - general = ConfigGeneral - search = ConfigSearch - providers = ConfigProviders - subtitles = ConfigSubtitles - postProcessing = ConfigPostProcessing - notifications = ConfigNotifications - anime = ConfigAnime + return 'ok' + def get_messages(self): + messages = {} + cur_notification_num = 1 + for cur_notification in ui.notifications.get_notifications(self.request.remote_ip): + messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title, + 'message': cur_notification.message, + 'type': cur_notification.type} + cur_notification_num += 1 -def haveXBMC(): - return sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY - - -def havePLEX(): - return sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY - - -def haveTORRENT(): - if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \ - and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https' - or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'): - return True - else: - return False - - -def HomeMenu(): - return [ - {'title': 'Add Shows', 'path': 'home/addShows/', }, - {'title': 'Manual Post-Processing', 'path': 'home/postprocess/'}, - {'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': haveXBMC}, - {'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': havePLEX}, - {'title': 'Manage Torrents', 'path': 'manage/manageTorrents', 'requires': haveTORRENT}, - {'title': 'Restart', 'path': 'home/restart/?pid=' + str(sickbeard.PID), 'confirm': True}, - {'title': 'Shutdown', 'path': 'home/shutdown/?pid=' + str(sickbeard.PID), 'confirm': True}, - ] - - -class HomePostProcess(MainHandler): - def index(self, *args, **kwargs): - - t = PageTemplate(headers=self.request.headers, file="home_postprocess.tmpl") - t.submenu = HomeMenu() - return _munge(t) - - def processEpisode(self, dir=None, nzbName=None, jobName=None, quiet=None, process_method=None, force=None, - is_priority=None, failed="0", type="auto", *args, **kwargs): - - if failed == "0": - failed = False - else: - failed = True - - if force in ["on", "1"]: - force = True - else: - force = False - - if is_priority in ["on", "1"]: - is_priority = True - else: - is_priority = False - - if not dir: - redirect("/home/postprocess/") - else: - result = processTV.processDir(dir, nzbName, process_method=process_method, force=force, - is_priority=is_priority, failed=failed, type=type) - if quiet is not None and int(quiet) == 1: - return result - - result = result.replace("\n", "
    \n") - return self._genericMessage("Postprocessing results", result) - - -class NewHomeAddShows(MainHandler): - def index(self, *args, **kwargs): - - t = PageTemplate(headers=self.request.headers, file="home_addShows.tmpl") - t.submenu = HomeMenu() - return _munge(t) - - def getIndexerLanguages(self, *args, **kwargs): - result = sickbeard.indexerApi().config['valid_languages'] - - # Make sure list is sorted alphabetically but 'en' is in front - if 'en' in result: - del result[result.index('en')] - result.sort() - result.insert(0, 'en') - - return json.dumps({'results': result}) - - def sanitizeFileName(self, name): - return helpers.sanitizeFileName(name) - - def searchIndexersForShowName(self, search_term, lang="en", indexer=None): - if not lang or lang == 'null': - lang = "en" - - search_term = search_term.encode('utf-8') - - results = {} - final_results = [] - - # Query Indexers for each search term and build the list of results - for indexer in sickbeard.indexerApi().indexers if not int(indexer) else [int(indexer)]: - lINDEXER_API_PARMS = sickbeard.indexerApi(indexer).api_params.copy() - lINDEXER_API_PARMS['language'] = lang - lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsListUI - t = sickbeard.indexerApi(indexer).indexer(**lINDEXER_API_PARMS) - - logger.log("Searching for Show with searchterm: %s on Indexer: %s" % ( - search_term, sickbeard.indexerApi(indexer).name), logger.DEBUG) - try: - # add search results - results.setdefault(indexer, []).extend(t[search_term]) - except Exception, e: - continue - - map(final_results.extend, - ([[sickbeard.indexerApi(id).name, id, sickbeard.indexerApi(id).config["show_url"], int(show['id']), - show['seriesname'], show['firstaired']] for show in shows] for id, shows in results.items())) - - lang_id = sickbeard.indexerApi().config['langabbv_to_id'][lang] - return json.dumps({'results': final_results, 'langid': lang_id}) - - def massAddTable(self, rootDir=None): - t = PageTemplate(headers=self.request.headers, file="home_massAddTable.tmpl") - t.submenu = HomeMenu() - - if not rootDir: - return "No folders selected." - elif type(rootDir) != list: - root_dirs = [rootDir] - else: - root_dirs = rootDir - - root_dirs = [urllib.unquote_plus(x) for x in root_dirs] - - if sickbeard.ROOT_DIRS: - default_index = int(sickbeard.ROOT_DIRS.split('|')[0]) - else: - default_index = 0 - - if len(root_dirs) > default_index: - tmp = root_dirs[default_index] - if tmp in root_dirs: - root_dirs.remove(tmp) - root_dirs = [tmp] + root_dirs - - dir_list = [] - - myDB = db.DBConnection() - for root_dir in root_dirs: - try: - file_list = ek.ek(os.listdir, root_dir) - except: - continue - - for cur_file in file_list: - - cur_path = ek.ek(os.path.normpath, ek.ek(os.path.join, root_dir, cur_file)) - if not ek.ek(os.path.isdir, cur_path): - continue - - cur_dir = { - 'dir': cur_path, - 'display_dir': '' + ek.ek(os.path.dirname, cur_path) + os.sep + '' + ek.ek( - os.path.basename, - cur_path), - } - - # see if the folder is in XBMC already - dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path]) - - if dirResults: - cur_dir['added_already'] = True - else: - cur_dir['added_already'] = False - - dir_list.append(cur_dir) - - indexer_id = show_name = indexer = None - for cur_provider in sickbeard.metadata_provider_dict.values(): - if indexer_id and show_name: - continue - - (indexer_id, show_name, indexer) = cur_provider.retrieveShowMetadata(cur_path) - - # default to TVDB if indexer was not detected - if show_name and not (indexer or indexer_id): - (sn, idx, id) = helpers.searchIndexerForShowID(show_name, indexer, indexer_id) - - # set indexer and indexer_id from found info - if not indexer and idx: - indexer = idx - - if not indexer_id and id: - indexer_id = id - - cur_dir['existing_info'] = (indexer_id, show_name, indexer) - - if indexer_id and helpers.findCertainShow(sickbeard.showList, indexer_id): - cur_dir['added_already'] = True - - t.dirList = dir_list - - return _munge(t) - - def newShow(self, show_to_add=None, other_shows=None, use_show_name=None): - """ - Display the new show page which collects a tvdb id, folder, and extra options and - posts them to addNewShow - """ - self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate') - self.set_header('Pragma', 'no-cache') - self.set_header('Expires', '0') - - t = PageTemplate(headers=self.request.headers, file="home_newShow.tmpl") - t.submenu = HomeMenu() - - indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add) - - if indexer_id and indexer and show_name: - use_provided_info = True - else: - use_provided_info = False - - # tell the template whether we're giving it show name & Indexer ID - t.use_provided_info = use_provided_info - - # use the given show_dir for the indexer search if available - if use_show_name: - t.default_show_name = show_name - elif not show_dir: - t.default_show_name = '' - elif not show_name: - t.default_show_name = ek.ek(os.path.basename, ek.ek(os.path.normpath, show_dir)).replace('.', ' ') - else: - t.default_show_name = show_name - - # carry a list of other dirs if given - if not other_shows: - other_shows = [] - elif type(other_shows) != list: - other_shows = [other_shows] - - if use_provided_info: - t.provided_indexer_id = int(indexer_id or 0) - t.provided_indexer_name = show_name - - t.provided_show_dir = show_dir - t.other_shows = other_shows - t.provided_indexer = int(indexer or sickbeard.INDEXER_DEFAULT) - t.indexers = sickbeard.indexerApi().indexers - t.whitelist = [] - t.blacklist = [] - t.groups = [] - - return _munge(t) - - def recommendedShows(self, *args, **kwargs): - """ - Display the new show page which collects a tvdb id, folder, and extra options and - posts them to addNewShow - """ - self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate') - self.set_header('Pragma', 'no-cache') - self.set_header('Expires', '0') - - t = PageTemplate(headers=self.request.headers, file="home_recommendedShows.tmpl") - t.submenu = HomeMenu() - - return _munge(t) - - def getRecommendedShows(self, *args, **kwargs): - final_results = [] - - logger.log(u"Getting recommended shows from Trakt.tv", logger.DEBUG) - recommendedlist = TraktCall("recommendations/shows.json/%API%", sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, - sickbeard.TRAKT_PASSWORD) - - if recommendedlist == 'NULL': - logger.log(u"No shows found in your recommendedlist, aborting recommendedlist update", logger.DEBUG) - return - - if recommendedlist is None: - logger.log(u"Could not connect to trakt service, aborting recommended list update", logger.ERROR) - return - - map(final_results.append, - ([show['url'], - show['title'], - show['overview'], - sbdatetime.sbdatetime.sbfdate(datetime.date.fromtimestamp(int(show['first_aired']))), - sickbeard.indexerApi(1).name, - sickbeard.indexerApi(1).config['icon'], - int(show['tvdb_id'] or 0), - '%s%s' % (sickbeard.indexerApi(1).config['show_url'], int(show['tvdb_id'] or 0)), - sickbeard.indexerApi(2).name, - sickbeard.indexerApi(2).config['icon'], - int(show['tvrage_id'] or 0), - '%s%s' % (sickbeard.indexerApi(2).config['show_url'], int(show['tvrage_id'] or 0)) - ] for show in recommendedlist if not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id'])))) - - self.set_header('Content-Type', 'application/json') - return json.dumps({'results': final_results}) - - def addRecommendedShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None, - anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None, - fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None, - scene=None): - - indexer = 1 - indexer_name = sickbeard.indexerApi(int(indexer)).name - show_url = whichSeries.split('|')[1] - indexer_id = whichSeries.split('|')[0] - show_name = whichSeries.split('|')[2] - - return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]), - indexerLang, rootDir, - defaultStatus, - anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows, - skipShow, providedIndexer, anime, scene) - - def trendingShows(self, *args, **kwargs): - """ - Display the new show page which collects a tvdb id, folder, and extra options and - posts them to addNewShow - """ - t = PageTemplate(headers=self.request.headers, file="home_trendingShows.tmpl") - t.submenu = HomeMenu() - - t.trending_shows = TraktCall("shows/trending.json/%API%", sickbeard.TRAKT_API_KEY) - t.trending_inlibrary = 0 - if None is not t.trending_shows: - for item in t.trending_shows: - tvdbs = ['tvdb_id', 'tvrage_id'] - for index, tvdb in enumerate(tvdbs): - try: - item[u'show_id'] = str(item[tvdb]) - tvshow = helpers.findCertainShow(sickbeard.showList, int(item[tvdb])) - except: - continue - # check tvshow indexer is not using the same id from another indexer - if tvshow and (index + 1) == tvshow.indexer: - item[u'show_id'] = u'%s:%s' % (tvshow.indexer, item[tvdb]) - t.trending_inlibrary += 1 - break - - return _munge(t) - - def existingShows(self, *args, **kwargs): - """ - Prints out the page to add existing shows from a root dir - """ - t = PageTemplate(headers=self.request.headers, file="home_addExistingShow.tmpl") - t.submenu = HomeMenu() - t.whitelist = [] - t.blacklist = [] - t.groups = [] - - return _munge(t) - - def addTraktShow(self, indexer_id, showName): - if helpers.findCertainShow(sickbeard.showList, int(indexer_id)): - return - return self.newShow('|'.join(['', '', indexer_id, showName]), use_show_name=True) - - def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None, - anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None, - fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None, - scene=None, blacklist=None, whitelist=None): - """ - Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are - provided then it forwards back to newShow, if not it goes to /home. - """ - - # grab our list of other dirs if given - if not other_shows: - other_shows = [] - elif type(other_shows) != list: - other_shows = [other_shows] - - def finishAddShow(): - # if there are no extra shows then go home - if not other_shows: - redirect('/home/') - - # peel off the next one - next_show_dir = other_shows[0] - rest_of_show_dirs = other_shows[1:] - - # go to add the next show - return self.newShow(next_show_dir, rest_of_show_dirs) - - # if we're skipping then behave accordingly - if skipShow: - return finishAddShow() - - # sanity check on our inputs - if (not rootDir and not fullShowPath) or not whichSeries: - return "Missing params, no Indexer ID or folder:" + repr(whichSeries) + " and " + repr( - rootDir) + "/" + repr(fullShowPath) - - # figure out what show we're adding and where - series_pieces = whichSeries.split('|') - if (whichSeries and rootDir) or (whichSeries and fullShowPath and len(series_pieces) > 1): - if len(series_pieces) < 6: - logger.log("Unable to add show due to show selection. Not anough arguments: %s" % (repr(series_pieces)), - logger.ERROR) - ui.notifications.error("Unknown error. Unable to add show due to problem with show selection.") - redirect('/home/addShows/existingShows/') - indexer = int(series_pieces[1]) - indexer_id = int(series_pieces[3]) - show_name = series_pieces[4] - else: - # if no indexer was provided use the default indexer set in General settings - if not providedIndexer: - providedIndexer = sickbeard.INDEXER_DEFAULT - - indexer = int(providedIndexer) - indexer_id = int(whichSeries) - show_name = os.path.basename(os.path.normpath(fullShowPath)) - - # use the whole path if it's given, or else append the show name to the root dir to get the full show path - if fullShowPath: - show_dir = ek.ek(os.path.normpath, fullShowPath) - else: - show_dir = ek.ek(os.path.join, rootDir, helpers.sanitizeFileName(show_name)) - - # blanket policy - if the dir exists you should have used "add existing show" numbnuts - if ek.ek(os.path.isdir, show_dir) and not fullShowPath: - ui.notifications.error("Unable to add show", "Folder " + show_dir + " exists already") - redirect('/home/addShows/existingShows/') - - # don't create show dir if config says not to - if sickbeard.ADD_SHOWS_WO_DIR: - logger.log(u"Skipping initial creation of " + show_dir + " due to config.ini setting") - else: - dir_exists = helpers.makeDir(show_dir) - if not dir_exists: - logger.log(u"Unable to create the folder " + show_dir + ", can't add the show", logger.ERROR) - ui.notifications.error("Unable to add show", - "Unable to create the folder " + show_dir + ", can't add the show") - redirect("/home/") - else: - helpers.chmodAsParent(show_dir) - - # prepare the inputs for passing along - scene = config.checkbox_to_value(scene) - anime = config.checkbox_to_value(anime) - flatten_folders = config.checkbox_to_value(flatten_folders) - subtitles = config.checkbox_to_value(subtitles) - - if whitelist: - whitelist = short_group_names(whitelist) - if blacklist: - blacklist = short_group_names(blacklist) - - if not anyQualities: - anyQualities = [] - if not bestQualities: - bestQualities = [] - if type(anyQualities) != list: - anyQualities = [anyQualities] - if type(bestQualities) != list: - bestQualities = [bestQualities] - newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities)) - - # add the show - sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality, - flatten_folders, indexerLang, subtitles, anime, - scene, None, blacklist, whitelist) # @UndefinedVariable - ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir) - - return finishAddShow() - - def split_extra_show(self, extra_show): - if not extra_show: - return (None, None, None, None) - split_vals = extra_show.split('|') - if len(split_vals) < 4: - indexer = split_vals[0] - show_dir = split_vals[1] - return (indexer, show_dir, None, None) - indexer = split_vals[0] - show_dir = split_vals[1] - indexer_id = split_vals[2] - show_name = '|'.join(split_vals[3:]) - - return (indexer, show_dir, indexer_id, show_name) - - def addExistingShows(self, shows_to_add=None, promptForSettings=None): - """ - Receives a dir list and add them. Adds the ones with given TVDB IDs first, then forwards - along to the newShow page. - """ - - # grab a list of other shows to add, if provided - if not shows_to_add: - shows_to_add = [] - elif type(shows_to_add) != list: - shows_to_add = [shows_to_add] - - shows_to_add = [urllib.unquote_plus(x) for x in shows_to_add] - - promptForSettings = config.checkbox_to_value(promptForSettings) - - indexer_id_given = [] - dirs_only = [] - # separate all the ones with Indexer IDs - for cur_dir in shows_to_add: - if '|' in cur_dir: - split_vals = cur_dir.split('|') - if len(split_vals) < 3: - dirs_only.append(cur_dir) - if not '|' in cur_dir: - dirs_only.append(cur_dir) - else: - indexer, show_dir, indexer_id, show_name = self.split_extra_show(cur_dir) - - if not show_dir or not indexer_id or not show_name: - continue - - indexer_id_given.append((int(indexer), show_dir, int(indexer_id), show_name)) - - - # if they want me to prompt for settings then I will just carry on to the newShow page - if promptForSettings and shows_to_add: - return self.newShow(shows_to_add[0], shows_to_add[1:]) - - # if they don't want me to prompt for settings then I can just add all the nfo shows now - num_added = 0 - for cur_show in indexer_id_given: - indexer, show_dir, indexer_id, show_name = cur_show - - if indexer is not None and indexer_id is not None: - # add the show - sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, - default_status=sickbeard.STATUS_DEFAULT, - quality=sickbeard.QUALITY_DEFAULT, - flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT, - subtitles=sickbeard.SUBTITLES_DEFAULT, - anime=sickbeard.ANIME_DEFAULT, - scene=sickbeard.SCENE_DEFAULT) - num_added += 1 - - if num_added: - ui.notifications.message("Shows Added", - "Automatically added " + str(num_added) + " from their existing metadata files") - - # if we're done then go home - if not dirs_only: - redirect('/home/') - - # for the remaining shows we need to prompt for each one, so forward this on to the newShow page - return self.newShow(dirs_only[0], dirs_only[1:]) - - -ErrorLogsMenu = [ - {'title': 'Clear Errors', 'path': 'errorlogs/clearerrors/'}, - # { 'title': 'View Log', 'path': 'errorlogs/viewlog' }, -] + return json.dumps(messages) class ErrorLogs(MainHandler): + @staticmethod + def ErrorLogsMenu(): + return [{'title': 'Clear Errors', 'path': 'errorlogs/clearerrors/'},] + def index(self, *args, **kwargs): - t = PageTemplate(headers=self.request.headers, file="errorlogs.tmpl") - t.submenu = ErrorLogsMenu + t = PageTemplate(headers=self.request.headers, file='errorlogs.tmpl') + t.submenu = self.ErrorLogsMenu - return _munge(t) + return t.respond() def clearerrors(self, *args, **kwargs): classes.ErrorViewer.clear() - redirect("/errorlogs/") + self.redirect('/errorlogs/') def viewlog(self, minLevel=logger.MESSAGE, maxLines=500): - t = PageTemplate(headers=self.request.headers, file="viewlogs.tmpl") - t.submenu = ErrorLogsMenu + t = PageTemplate(headers=self.request.headers, file='viewlogs.tmpl') + t.submenu = self.ErrorLogsMenu minLevel = int(minLevel) @@ -3172,7 +4362,7 @@ class ErrorLogs(MainHandler): with ek.ek(open, logger.sb_log_instance.log_file_path) as f: data = f.readlines() - regex = "^(\d\d\d\d)\-(\d\d)\-(\d\d)\s*(\d\d)\:(\d\d):(\d\d)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$" + regex = '^(\d\d\d\d)\-(\d\d)\-(\d\d)\s*(\d\d)\:(\d\d):(\d\d)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$' finalData = [] @@ -3199,1286 +4389,61 @@ class ErrorLogs(MainHandler): continue elif lastLine: - finalData.append("AA" + x) + finalData.append('AA' + x) numLines += 1 if numLines >= numToShow: break - result = "".join(finalData) + result = ''.join(finalData) t.logLines = result t.minLevel = minLevel - return _munge(t) + return t.respond() -class Home(MainHandler): - def index(self, *args, **kwargs): +class WebFileBrowser(MainHandler): + def index(self, path='', includeFiles=False, *args, **kwargs): + self.set_header('Content-Type', 'application/json') + return json.dumps(foldersAtPath(path, True, bool(int(includeFiles)))) - t = PageTemplate(headers=self.request.headers, file="home.tmpl") - if sickbeard.ANIME_SPLIT_HOME: - shows = [] - anime = [] - for show in sickbeard.showList: - if show.is_anime: - anime.append(show) - else: - shows.append(show) - t.showlists = [["Shows", shows], - ["Anime", anime]] - else: - t.showlists = [["Shows", sickbeard.showList]] + def complete(self, term, includeFiles=0): + self.set_header('Content-Type', 'application/json') + paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term), includeFiles=bool(int(includeFiles))) if 'path' in entry] + return json.dumps(paths) - t.submenu = HomeMenu() - return _munge(t) - - addShows = NewHomeAddShows - postprocess = HomePostProcess - - def testSABnzbd(self, host=None, username=None, password=None, apikey=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_url(host) - - connection, accesMsg = sab.getSabAccesMethod(host, username, password, apikey) - if connection: - authed, authMsg = sab.testAuthentication(host, username, password, apikey) # @UnusedVariable - if authed: - return "Success. Connected and authenticated" - else: - return "Authentication failed. SABnzbd expects '" + accesMsg + "' as authentication method" - else: - return "Unable to connect to host" - - def testTorrent(self, torrent_method=None, host=None, username=None, password=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_url(host) - - client = clients.getClientIstance(torrent_method) - - connection, accesMsg = client(host, username, password).testAuthentication() - - return accesMsg - - def testGrowl(self, host=None, password=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_host(host, default_port=23053) - - result = notifiers.growl_notifier.test_notify(host, password) - if password is None or password == '': - pw_append = '' - else: - pw_append = " with password: " + password - - if result: - return "Registered and Tested growl successfully " + urllib.unquote_plus(host) + pw_append - else: - return "Registration and Testing of growl failed " + urllib.unquote_plus(host) + pw_append - - def testProwl(self, prowl_api=None, prowl_priority=0): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.prowl_notifier.test_notify(prowl_api, prowl_priority) - if result: - return "Test prowl notice sent successfully" - else: - return "Test prowl notice failed" - - def testBoxcar2(self, accesstoken=None, sound=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.boxcar2_notifier.test_notify(accesstoken, sound) - if result: - return "Boxcar2 notification succeeded. Check your Boxcar2 clients to make sure it worked" - else: - return "Error sending Boxcar2 notification" - - def testPushover(self, userKey=None, apiKey=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.pushover_notifier.test_notify(userKey, apiKey) - if result: - return "Pushover notification succeeded. Check your Pushover clients to make sure it worked" - else: - return "Error sending Pushover notification" - - def twitterStep1(self, *args, **kwargs): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - return notifiers.twitter_notifier._get_authorization() - - def twitterStep2(self, key): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.twitter_notifier._get_credentials(key) - logger.log(u"result: " + str(result)) - if result: - return "Key verification successful" - else: - return "Unable to verify key" - - def testTwitter(self, *args, **kwargs): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.twitter_notifier.test_notify() - if result: - return "Tweet successful, check your twitter to make sure it worked" - else: - return "Error sending tweet" - - def testXBMC(self, host=None, username=None, password=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_hosts(host) - finalResult = '' - for curHost in [x.strip() for x in host.split(",")]: - curResult = notifiers.xbmc_notifier.test_notify(urllib.unquote_plus(curHost), username, password) - if len(curResult.split(":")) > 2 and 'OK' in curResult.split(":")[2]: - finalResult += "Test XBMC notice sent successfully to " + urllib.unquote_plus(curHost) - else: - finalResult += "Test XBMC notice failed to " + urllib.unquote_plus(curHost) - finalResult += "
    \n" - - return finalResult - - def testPMC(self, host=None, username=None, password=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - finalResult = '' - for curHost in [x.strip() for x in host.split(',')]: - curResult = notifiers.plex_notifier.test_notify_pmc(urllib.unquote_plus(curHost), username, password) - if len(curResult.split(':')) > 2 and 'OK' in curResult.split(':')[2]: - finalResult += 'Successful test notice sent to Plex client ... ' + urllib.unquote_plus(curHost) - else: - finalResult += 'Test failed for Plex client ... ' + urllib.unquote_plus(curHost) - finalResult += '
    ' + "\n" - - ui.notifications.message('Tested Plex client(s): ', urllib.unquote_plus(host.replace(',', ', '))) - - return finalResult - - def testPMS(self, host=None, username=None, password=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - finalResult = '' - - curResult = notifiers.plex_notifier.test_notify_pms(urllib.unquote_plus(host), username, password) - if None is curResult: - finalResult += 'Successful test of Plex server(s) ... ' + urllib.unquote_plus(host.replace(',', ', ')) - else: - finalResult += 'Test failed for Plex server(s) ... ' + urllib.unquote_plus(curResult.replace(',', ', ')) - finalResult += '
    ' + "\n" - - ui.notifications.message('Tested Plex Media Server host(s): ', urllib.unquote_plus(host.replace(',', ', '))) - - return finalResult - - def testLibnotify(self, *args, **kwargs): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - if notifiers.libnotify_notifier.test_notify(): - return "Tried sending desktop notification via libnotify" - else: - return notifiers.libnotify.diagnose() - - def testNMJ(self, host=None, database=None, mount=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_host(host) - result = notifiers.nmj_notifier.test_notify(urllib.unquote_plus(host), database, mount) - if result: - return "Successfully started the scan update" - else: - return "Test failed to start the scan update" - - def settingsNMJ(self, host=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_host(host) - result = notifiers.nmj_notifier.notify_settings(urllib.unquote_plus(host)) - if result: - return '{"message": "Got settings from %(host)s", "database": "%(database)s", "mount": "%(mount)s"}' % { - "host": host, "database": sickbeard.NMJ_DATABASE, "mount": sickbeard.NMJ_MOUNT} - else: - return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}' - - def testNMJv2(self, host=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_host(host) - result = notifiers.nmjv2_notifier.test_notify(urllib.unquote_plus(host)) - if result: - return "Test notice sent successfully to " + urllib.unquote_plus(host) - else: - return "Test notice failed to " + urllib.unquote_plus(host) - - def settingsNMJv2(self, host=None, dbloc=None, instance=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_host(host) - result = notifiers.nmjv2_notifier.notify_settings(urllib.unquote_plus(host), dbloc, instance) - if result: - return '{"message": "NMJ Database found at: %(host)s", "database": "%(database)s"}' % {"host": host, - "database": sickbeard.NMJv2_DATABASE} - else: - return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % { - "dbloc": dbloc} - - def testTrakt(self, api=None, username=None, password=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.trakt_notifier.test_notify(api, username, password) - if result: - return "Test notice sent successfully to Trakt" - else: - return "Test notice failed to Trakt" - - def loadShowNotifyLists(self, *args, **kwargs): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - myDB = db.DBConnection() - rows = myDB.select("SELECT show_id, show_name, notify_list FROM tv_shows ORDER BY show_name ASC") - - data = {} - size = 0 - for r in rows: - data[r['show_id']] = {'id': r['show_id'], 'name': r['show_name'], 'list': r['notify_list']} - size += 1 - data['_size'] = size - return json.dumps(data) - - def testEmail(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - host = config.clean_host(host) - if notifiers.email_notifier.test_notify(host, port, smtp_from, use_tls, user, pwd, to): - return 'Test email sent successfully! Check inbox.' - else: - return 'ERROR: %s' % notifiers.email_notifier.last_err - - def testNMA(self, nma_api=None, nma_priority=0): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.nma_notifier.test_notify(nma_api, nma_priority) - if result: - return "Test NMA notice sent successfully" - else: - return "Test NMA notice failed" - - def testPushalot(self, authorizationToken=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.pushalot_notifier.test_notify(authorizationToken) - if result: - return "Pushalot notification succeeded. Check your Pushalot clients to make sure it worked" - else: - return "Error sending Pushalot notification" - - def testPushbullet(self, api=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.pushbullet_notifier.test_notify(api) - if result: - return "Pushbullet notification succeeded. Check your device to make sure it worked" - else: - return "Error sending Pushbullet notification" - - def getPushbulletDevices(self, api=None): - self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') - - result = notifiers.pushbullet_notifier.get_devices(api) - if result: - return result - else: - return "Error sending Pushbullet notification" - - def shutdown(self, pid=None): - - if str(pid) != str(sickbeard.PID): - redirect("/home/") - - sickbeard.events.put(sickbeard.events.SystemEvent.SHUTDOWN) - - title = "Shutting down" - message = "SickGear is shutting down..." - - return self._genericMessage(title, message) - - def restart(self, pid=None): - - if str(pid) != str(sickbeard.PID): - redirect("/home/") - - t = PageTemplate(headers=self.request.headers, file="restart.tmpl") - t.submenu = HomeMenu() - - # restart - sickbeard.events.put(sickbeard.events.SystemEvent.RESTART) - - return _munge(t) - - def update(self, pid=None): - - if str(pid) != str(sickbeard.PID): - redirect("/home/") - - updated = sickbeard.versionCheckScheduler.action.update() # @UndefinedVariable - if updated: - # do a hard restart - sickbeard.events.put(sickbeard.events.SystemEvent.RESTART) - - t = PageTemplate(headers=self.request.headers, file="restart_bare.tmpl") - return _munge(t) - else: - return self._genericMessage("Update Failed", - "Update wasn't successful, not restarting. Check your log for more information.") - - def branchCheckout(self, branch): - sickbeard.BRANCH = branch - ui.notifications.message('Checking out branch: ', branch) - return self.update(sickbeard.PID) - - def pullRequestCheckout(self, branch): - pull_request = branch - branch = branch.split(':')[1] - fetched = sickbeard.versionCheckScheduler.action.fetch(pull_request) - if fetched: - sickbeard.BRANCH = branch - ui.notifications.message('Checking out branch: ', branch) - return self.update(sickbeard.PID) - else: - return redirect('/home/') - - def displayShow(self, show=None): - - if show is None: - return self._genericMessage("Error", "Invalid show ID") - else: - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return self._genericMessage("Error", "Show not in show list") - - myDB = db.DBConnection() - seasonResults = myDB.select( - "SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season desc", - [showObj.indexerid] - ) - - sqlResults = myDB.select( - "SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC", - [showObj.indexerid] - ) - - t = PageTemplate(headers=self.request.headers, file="displayShow.tmpl") - t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}] - - try: - t.showLoc = (showObj.location, True) - except sickbeard.exceptions.ShowDirNotFoundException: - t.showLoc = (showObj._location, False) - - show_message = '' - - if sickbeard.showQueueScheduler.action.isBeingAdded(showObj): # @UndefinedVariable - show_message = 'This show is in the process of being downloaded - the info below is incomplete.' - - elif sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable - show_message = 'The information on this page is in the process of being updated.' - - elif sickbeard.showQueueScheduler.action.isBeingRefreshed(showObj): # @UndefinedVariable - show_message = 'The episodes below are currently being refreshed from disk' - - elif sickbeard.showQueueScheduler.action.isBeingSubtitled(showObj): # @UndefinedVariable - show_message = 'Currently downloading subtitles for this show' - - elif sickbeard.showQueueScheduler.action.isInRefreshQueue(showObj): # @UndefinedVariable - show_message = 'This show is queued to be refreshed.' - - elif sickbeard.showQueueScheduler.action.isInUpdateQueue(showObj): # @UndefinedVariable - show_message = 'This show is queued and awaiting an update.' - - elif sickbeard.showQueueScheduler.action.isInSubtitleQueue(showObj): # @UndefinedVariable - show_message = 'This show is queued and awaiting subtitles download.' - - if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj): # @UndefinedVariable - if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable - t.submenu.append( - {'title': 'Remove', 'path': 'home/deleteShow?show=%d' % showObj.indexerid, 'confirm': True}) - t.submenu.append({'title': 'Re-scan files', 'path': 'home/refreshShow?show=%d' % showObj.indexerid}) - t.submenu.append( - {'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&force=1' % showObj.indexerid}) - t.submenu.append({'title': 'Update show in XBMC', - 'path': 'home/updateXBMC?showName=%s' % urllib.quote_plus( - showObj.name.encode('utf-8')), 'requires': haveXBMC}) - t.submenu.append({'title': 'Preview Rename', 'path': 'home/testRename?show=%d' % showObj.indexerid}) - if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled( - showObj) and showObj.subtitles: - t.submenu.append( - {'title': 'Download Subtitles', 'path': 'home/subtitleShow?show=%d' % showObj.indexerid}) - - t.show = showObj - t.sqlResults = sqlResults - t.seasonResults = seasonResults - t.show_message = show_message - - epCounts = {} - epCats = {} - epCounts[Overview.SKIPPED] = 0 - epCounts[Overview.WANTED] = 0 - epCounts[Overview.QUAL] = 0 - epCounts[Overview.GOOD] = 0 - epCounts[Overview.UNAIRED] = 0 - epCounts[Overview.SNATCHED] = 0 - - for curResult in sqlResults: - curEpCat = showObj.getOverview(int(curResult["status"])) - if curEpCat: - epCats[str(curResult["season"]) + "x" + str(curResult["episode"])] = curEpCat - epCounts[curEpCat] += 1 +class ApiBuilder(MainHandler): + def index(self): + """ expose the api-builder template """ + t = PageTemplate(headers=self.request.headers, file='apiBuilder.tmpl') def titler(x): return (remove_article(x), x)[not x or sickbeard.SORT_ARTICLE] - if sickbeard.ANIME_SPLIT_HOME: - shows = [] - anime = [] - for show in sickbeard.showList: - if show.is_anime: - anime.append(show) - else: - shows.append(show) - t.sortedShowLists = [["Shows", sorted(shows, lambda x, y: cmp(titler(x.name), titler(y.name)))], - ["Anime", sorted(anime, lambda x, y: cmp(titler(x.name), titler(y.name)))]] + t.sortedShowList = sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name))) + seasonSQLResults = {} + episodeSQLResults = {} + + myDB = db.DBConnection(row_type='dict') + for curShow in t.sortedShowList: + seasonSQLResults[curShow.indexerid] = myDB.select( + 'SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC', [curShow.indexerid]) + + for curShow in t.sortedShowList: + episodeSQLResults[curShow.indexerid] = myDB.select( + 'SELECT DISTINCT season,episode FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC', + [curShow.indexerid]) + + t.seasonSQLResults = seasonSQLResults + t.episodeSQLResults = episodeSQLResults + + if len(sickbeard.API_KEY) == 32: + t.apikey = sickbeard.API_KEY else: - t.sortedShowLists = [ - ["Shows", sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))]] + t.apikey = 'api key not generated' - tvshows = [] - for tvshow_types in t.sortedShowLists: - for tvshow in tvshow_types[1]: - tvshows.append(tvshow.indexerid) - t.tvshow_id_csv = ','.join(str(x) for x in tvshows) - - t.bwl = None - if showObj.is_anime: - t.bwl = showObj.release_groups - - t.epCounts = epCounts - t.epCats = epCats - - showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid) - - indexerid = int(showObj.indexerid) - indexer = int(showObj.indexer) - t.all_scene_exceptions = showObj.exceptions - t.scene_numbering = get_scene_numbering_for_show(indexerid, indexer) - t.xem_numbering = get_xem_numbering_for_show(indexerid, indexer) - t.scene_absolute_numbering = get_scene_absolute_numbering_for_show(indexerid, indexer) - t.xem_absolute_numbering = get_xem_absolute_numbering_for_show(indexerid, indexer) - - return _munge(t) - - def plotDetails(self, show, season, episode): - myDB = db.DBConnection() - result = myDB.select( - "SELECT description FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?", - (int(show), int(season), int(episode))) - return result[0]['description'] if result else 'Episode not found.' - - def sceneExceptions(self, show): - exceptionsList = sickbeard.scene_exceptions.get_all_scene_exceptions(show) - if not exceptionsList: - return "No scene exceptions" - - out = [] - for season, names in iter(sorted(exceptionsList.iteritems())): - if season == -1: - season = "*" - out.append("S" + str(season) + ": " + ", ".join(names)) - return "
    ".join(out) - - def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[], - flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None, - indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None, - rls_require_words=None, anime=None, blacklist=None, whitelist=None, - scene=None): - - if show is None: - errString = "Invalid show ID: " + str(show) - if directCall: - return [errString] - else: - return self._genericMessage("Error", errString) - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if not showObj: - errString = "Unable to find the specified show: " + str(show) - if directCall: - return [errString] - else: - return self._genericMessage("Error", errString) - - showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid) - - if not location and not anyQualities and not bestQualities and not flatten_folders: - t = PageTemplate(headers=self.request.headers, file="editShow.tmpl") - t.submenu = HomeMenu() - - if showObj.is_anime: - t.whitelist = showObj.release_groups.whitelist - t.blacklist = showObj.release_groups.blacklist - - t.groups = [] - if helpers.set_up_anidb_connection(): - try: - anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=showObj.name) - t.groups = anime.get_groups() - except Exception, e: - t.groups.append(dict([('name', 'Fail:AniDB connect. Restart sg else check debug log'), ('rating', ''), ('range', '')])) - else: - t.groups.append(dict([('name', 'Did not initialise AniDB. Check debug log if reqd.'), ('rating', ''), ('range', '')])) - - with showObj.lock: - t.show = showObj - t.scene_exceptions = get_scene_exceptions(showObj.indexerid) - - return _munge(t) - - flatten_folders = config.checkbox_to_value(flatten_folders) - dvdorder = config.checkbox_to_value(dvdorder) - archive_firstmatch = config.checkbox_to_value(archive_firstmatch) - paused = config.checkbox_to_value(paused) - air_by_date = config.checkbox_to_value(air_by_date) - scene = config.checkbox_to_value(scene) - sports = config.checkbox_to_value(sports) - anime = config.checkbox_to_value(anime) - subtitles = config.checkbox_to_value(subtitles) - - if indexerLang and indexerLang in sickbeard.indexerApi(showObj.indexer).indexer().config['valid_languages']: - indexer_lang = indexerLang - else: - indexer_lang = showObj.lang - - # if we changed the language then kick off an update - if indexer_lang == showObj.lang: - do_update = False - else: - do_update = True - - if scene == showObj.scene and anime == showObj.anime: - do_update_scene_numbering = False - else: - do_update_scene_numbering = True - - if type(anyQualities) != list: - anyQualities = [anyQualities] - - if type(bestQualities) != list: - bestQualities = [bestQualities] - - if type(exceptions_list) != list: - exceptions_list = [exceptions_list] - - # If directCall from mass_edit_update no scene exceptions handling or blackandwhite list handling - if directCall: - do_update_exceptions = False - else: - if set(exceptions_list) == set(showObj.exceptions): - do_update_exceptions = False - else: - do_update_exceptions = True - - with showObj.lock: - if anime: - if not showObj.release_groups: - showObj.release_groups = BlackAndWhiteList(showObj.indexerid) - if whitelist: - shortwhitelist = short_group_names(whitelist) - showObj.release_groups.set_white_keywords(shortwhitelist) - else: - showObj.release_groups.set_white_keywords([]) - - if blacklist: - shortblacklist = short_group_names(blacklist) - showObj.release_groups.set_black_keywords(shortblacklist) - else: - showObj.release_groups.set_black_keywords([]) - - errors = [] - with showObj.lock: - newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities)) - showObj.quality = newQuality - showObj.archive_firstmatch = archive_firstmatch - - # reversed for now - if bool(showObj.flatten_folders) != bool(flatten_folders): - showObj.flatten_folders = flatten_folders - try: - sickbeard.showQueueScheduler.action.refreshShow(showObj) # @UndefinedVariable - except exceptions.CantRefreshException, e: - errors.append("Unable to refresh this show: " + ex(e)) - - showObj.paused = paused - showObj.scene = scene - showObj.anime = anime - showObj.sports = sports - showObj.subtitles = subtitles - showObj.air_by_date = air_by_date - - if not directCall: - showObj.lang = indexer_lang - showObj.dvdorder = dvdorder - showObj.rls_ignore_words = rls_ignore_words.strip() - showObj.rls_require_words = rls_require_words.strip() - - # if we change location clear the db of episodes, change it, write to db, and rescan - if os.path.normpath(showObj._location) != os.path.normpath(location): - logger.log(os.path.normpath(showObj._location) + " != " + os.path.normpath(location), logger.DEBUG) - if not ek.ek(os.path.isdir, location) and not sickbeard.CREATE_MISSING_SHOW_DIRS: - errors.append("New location %s does not exist" % location) - - # don't bother if we're going to update anyway - elif not do_update: - # change it - try: - showObj.location = location - try: - sickbeard.showQueueScheduler.action.refreshShow(showObj) # @UndefinedVariable - except exceptions.CantRefreshException, e: - errors.append("Unable to refresh this show:" + ex(e)) - # grab updated info from TVDB - # showObj.loadEpisodesFromIndexer() - # rescan the episodes in the new folder - except exceptions.NoNFOException: - errors.append( - "The folder at %s doesn't contain a tvshow.nfo - copy your files to that folder before you change the directory in SickGear." % location) - - # save it to the DB - showObj.saveToDB() - - # force the update - if do_update: - try: - sickbeard.showQueueScheduler.action.updateShow(showObj, True) # @UndefinedVariable - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - except exceptions.CantUpdateException, e: - errors.append("Unable to force an update on the show.") - - if do_update_exceptions: - try: - scene_exceptions.update_scene_exceptions(showObj.indexerid, exceptions_list) # @UndefinedVdexerid) - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - except exceptions.CantUpdateException, e: - errors.append("Unable to force an update on scene exceptions of the show.") - - if do_update_scene_numbering: - try: - sickbeard.scene_numbering.xem_refresh(showObj.indexerid, showObj.indexer) # @UndefinedVariable - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - except exceptions.CantUpdateException, e: - errors.append("Unable to force an update on scene numbering of the show.") - - if directCall: - return errors - - if len(errors) > 0: - ui.notifications.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"), - '
      ' + '\n'.join(['
    • %s
    • ' % error for error in errors]) + "
    ") - - redirect("/home/displayShow?show=" + show) - - def deleteShow(self, show=None, full=0): - - if show is None: - return self._genericMessage("Error", "Invalid show ID") - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return self._genericMessage("Error", "Unable to find the specified show") - - if sickbeard.showQueueScheduler.action.isBeingAdded( - showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable - return self._genericMessage("Error", "Shows can't be deleted while they're being added or updated.") - - if sickbeard.USE_TRAKT and sickbeard.TRAKT_SYNC: - # remove show from trakt.tv library - sickbeard.traktCheckerScheduler.action.removeShowFromTraktLibrary(showObj) - - showObj.deleteShow(bool(full)) - - ui.notifications.message('%s with %s' % (('Deleting', 'Trashing')[sickbeard.TRASH_REMOVE_SHOW], - ('media left untouched', 'all related media')[bool(full)]), - '%s' % showObj.name) - redirect("/home/") - - def refreshShow(self, show=None): - - if show is None: - return self._genericMessage("Error", "Invalid show ID") - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return self._genericMessage("Error", "Unable to find the specified show") - - # force the update from the DB - try: - sickbeard.showQueueScheduler.action.refreshShow(showObj) # @UndefinedVariable - except exceptions.CantRefreshException, e: - ui.notifications.error("Unable to refresh this show.", - ex(e)) - - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - - redirect("/home/displayShow?show=" + str(showObj.indexerid)) - - def updateShow(self, show=None, force=0): - - if show is None: - return self._genericMessage("Error", "Invalid show ID") - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return self._genericMessage("Error", "Unable to find the specified show") - - # force the update - try: - sickbeard.showQueueScheduler.action.updateShow(showObj, bool(force)) # @UndefinedVariable - except exceptions.CantUpdateException, e: - ui.notifications.error("Unable to update this show.", - ex(e)) - - # just give it some time - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - - redirect("/home/displayShow?show=" + str(showObj.indexerid)) - - def subtitleShow(self, show=None, force=0): - - if show is None: - return self._genericMessage("Error", "Invalid show ID") - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return self._genericMessage("Error", "Unable to find the specified show") - - # search and download subtitles - sickbeard.showQueueScheduler.action.downloadSubtitles(showObj, bool(force)) # @UndefinedVariable - - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - - redirect("/home/displayShow?show=" + str(showObj.indexerid)) - - def updateXBMC(self, showName=None): - - # only send update to first host in the list -- workaround for xbmc sql backend users - if sickbeard.XBMC_UPDATE_ONLYFIRST: - # only send update to first host in the list -- workaround for xbmc sql backend users - host = sickbeard.XBMC_HOST.split(",")[0].strip() - else: - host = sickbeard.XBMC_HOST - - if notifiers.xbmc_notifier.update_library(showName=showName): - ui.notifications.message("Library update command sent to XBMC host(s): " + host) - else: - ui.notifications.error("Unable to contact one or more XBMC host(s): " + host) - redirect('/home/') - - def updatePLEX(self, *args, **kwargs): - result = notifiers.plex_notifier.update_library() - if None is result: - ui.notifications.message( - 'Library update command sent to', 'Plex Media Server host(s): ' + sickbeard.PLEX_SERVER_HOST.replace(',', ', ')) - else: - ui.notifications.error('Unable to contact', 'Plex Media Server host(s): ' + result.replace(',', ', ')) - redirect('/home/') - - def setStatus(self, show=None, eps=None, status=None, direct=False): - - if show is None or eps is None or status is None: - errMsg = "You must specify a show and at least one episode" - if direct: - ui.notifications.error('Error', errMsg) - return json.dumps({'result': 'error'}) - else: - return self._genericMessage("Error", errMsg) - - if not statusStrings.has_key(int(status)): - errMsg = "Invalid status" - if direct: - ui.notifications.error('Error', errMsg) - return json.dumps({'result': 'error'}) - else: - return self._genericMessage("Error", errMsg) - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - errMsg = "Error", "Show not in show list" - if direct: - ui.notifications.error('Error', errMsg) - return json.dumps({'result': 'error'}) - else: - return self._genericMessage("Error", errMsg) - - segments = {} - if eps is not None: - - sql_l = [] - for curEp in eps.split('|'): - - logger.log(u"Attempting to set status on episode " + curEp + " to " + status, logger.DEBUG) - - epInfo = curEp.split('x') - - epObj = showObj.getEpisode(int(epInfo[0]), int(epInfo[1])) - - if epObj is None: - return self._genericMessage("Error", "Episode couldn't be retrieved") - - if int(status) in [WANTED, FAILED]: - # figure out what episodes are wanted so we can backlog them - if epObj.season in segments: - segments[epObj.season].append(epObj) - else: - segments[epObj.season] = [epObj] - - with epObj.lock: - # don't let them mess up UNAIRED episodes - if epObj.status == UNAIRED: - logger.log(u"Refusing to change status of " + curEp + " because it is UNAIRED", logger.ERROR) - continue - - if int( - status) in Quality.DOWNLOADED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED + [ - IGNORED] and not ek.ek(os.path.isfile, epObj.location): - logger.log( - u"Refusing to change status of " + curEp + " to DOWNLOADED because it's not SNATCHED/DOWNLOADED", - logger.ERROR) - continue - - if int( - status) == FAILED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED: - logger.log( - u"Refusing to change status of " + curEp + " to FAILED because it's not SNATCHED/DOWNLOADED", - logger.ERROR) - continue - - epObj.status = int(status) - - # mass add to database - result = epObj.get_sql() - if None is not result: - sql_l.append(result) - - if len(sql_l) > 0: - myDB = db.DBConnection() - myDB.mass_action(sql_l) - - if int(status) == WANTED: - msg = "Backlog was automatically started for the following seasons of " + showObj.name + ":
    " - msg += '
      ' - - for season, segment in segments.items(): - cur_backlog_queue_item = search_queue.BacklogQueueItem(showObj, segment) - sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item) # @UndefinedVariable - - msg += "
    • Season " + str(season) + "
    • " - logger.log(u"Sending backlog for " + showObj.name + " season " + str( - season) + " because some eps were set to wanted") - - msg += "
    " - - if segments: - ui.notifications.message("Backlog started", msg) - - if int(status) == FAILED: - msg = "Retrying Search was automatically started for the following season of " + showObj.name + ":
    " - msg += '
      ' - - for season, segment in segments.items(): - cur_failed_queue_item = search_queue.FailedQueueItem(showObj, [segment]) - sickbeard.searchQueueScheduler.action.add_item(cur_failed_queue_item) # @UndefinedVariable - - msg += "
    • Season " + str(season) + "
    • " - logger.log(u"Retrying Search for " + showObj.name + " season " + str( - season) + " because some eps were set to failed") - - msg += "
    " - - if segments: - ui.notifications.message("Retry Search started", msg) - - if direct: - return json.dumps({'result': 'success'}) - else: - redirect("/home/displayShow?show=" + show) - - def testRename(self, show=None): - - if show is None: - return self._genericMessage("Error", "You must specify a show") - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj is None: - return self._genericMessage("Error", "Show not in show list") - - try: - show_loc = showObj.location # @UnusedVariable - except exceptions.ShowDirNotFoundException: - return self._genericMessage("Error", "Can't rename episodes when the show dir is missing.") - - ep_obj_rename_list = [] - - ep_obj_list = showObj.getAllEpisodes(has_location=True) - - for cur_ep_obj in ep_obj_list: - # Only want to rename if we have a location - if cur_ep_obj.location: - if cur_ep_obj.relatedEps: - # do we have one of multi-episodes in the rename list already - have_already = False - for cur_related_ep in cur_ep_obj.relatedEps + [cur_ep_obj]: - if cur_related_ep in ep_obj_rename_list: - have_already = True - break - if not have_already: - ep_obj_rename_list.append(cur_ep_obj) - else: - ep_obj_rename_list.append(cur_ep_obj) - - if ep_obj_rename_list: - # present season DESC episode DESC on screen - ep_obj_rename_list.reverse() - - t = PageTemplate(headers=self.request.headers, file="testRename.tmpl") - t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}] - t.ep_obj_list = ep_obj_rename_list - t.show = showObj - - return _munge(t) - - def doRename(self, show=None, eps=None): - - if show is None or eps is None: - errMsg = "You must specify a show and at least one episode" - return self._genericMessage("Error", errMsg) - - show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if show_obj is None: - errMsg = "Error", "Show not in show list" - return self._genericMessage("Error", errMsg) - - try: - show_loc = show_obj.location # @UnusedVariable - except exceptions.ShowDirNotFoundException: - return self._genericMessage("Error", "Can't rename episodes when the show dir is missing.") - - if eps is None: - redirect("/home/displayShow?show=" + show) - - myDB = db.DBConnection() - for curEp in eps.split('|'): - - epInfo = curEp.split('x') - - # this is probably the worst possible way to deal with double eps but I've kinda painted myself into a corner here with this stupid database - ep_result = myDB.select( - "SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND 5=5", - [show, epInfo[0], epInfo[1]]) - if not ep_result: - logger.log(u"Unable to find an episode for " + curEp + ", skipping", logger.WARNING) - continue - related_eps_result = myDB.select("SELECT * FROM tv_episodes WHERE location = ? AND episode != ?", - [ep_result[0]["location"], epInfo[1]]) - - root_ep_obj = show_obj.getEpisode(int(epInfo[0]), int(epInfo[1])) - root_ep_obj.relatedEps = [] - - for cur_related_ep in related_eps_result: - related_ep_obj = show_obj.getEpisode(int(cur_related_ep["season"]), int(cur_related_ep["episode"])) - if related_ep_obj not in root_ep_obj.relatedEps: - root_ep_obj.relatedEps.append(related_ep_obj) - - root_ep_obj.rename() - - redirect("/home/displayShow?show=" + show) - - def searchEpisode(self, show=None, season=None, episode=None): - - # retrieve the episode object and fail if we can't get one - ep_obj = _getEpisode(show, season, episode) - if isinstance(ep_obj, str): - return json.dumps({'result': 'failure'}) - - # make a queue item for it and put it on the queue - ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj.show, ep_obj) - - sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) # @UndefinedVariable - - if ep_queue_item.success: - return returnManualSearchResult(ep_queue_item) - if not ep_queue_item.started and ep_queue_item.success is None: - return json.dumps({'result': 'success'}) #I Actually want to call it queued, because the search hasnt been started yet! - if ep_queue_item.started and ep_queue_item.success is None: - return json.dumps({'result': 'success'}) - else: - return json.dumps({'result': 'failure'}) - - ### Returns the current ep_queue_item status for the current viewed show. - # Possible status: Downloaded, Snatched, etc... - # Returns {'show': 279530, 'episodes' : ['episode' : 6, 'season' : 1, 'searchstatus' : 'queued', 'status' : 'running', 'quality': '4013'] - def getManualSearchStatus(self, show=None, season=None): - - episodes = [] - currentManualSearchThreadsQueued = [] - currentManualSearchThreadActive = [] - finishedManualSearchThreadItems= [] - - # Queued Searches - currentManualSearchThreadsQueued = sickbeard.searchQueueScheduler.action.get_all_ep_from_queue(show) - # Running Searches - if (sickbeard.searchQueueScheduler.action.is_manualsearch_in_progress()): - currentManualSearchThreadActive = sickbeard.searchQueueScheduler.action.currentItem - - # Finished Searches - finishedManualSearchThreadItems = sickbeard.search_queue.MANUAL_SEARCH_HISTORY - - if currentManualSearchThreadsQueued: - for searchThread in currentManualSearchThreadsQueued: - searchstatus = 'queued' - if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem): - episodes.append({'episode': searchThread.segment.episode, - 'episodeindexid': searchThread.segment.indexerid, - 'season' : searchThread.segment.season, - 'searchstatus' : searchstatus, - 'status' : statusStrings[searchThread.segment.status], - 'quality': self.getQualityClass(searchThread.segment)}) - else: - for epObj in searchThread.segment: - episodes.append({'episode': epObj.episode, - 'episodeindexid': epObj.indexerid, - 'season' : epObj.season, - 'searchstatus' : searchstatus, - 'status' : statusStrings[epObj.status], - 'quality': self.getQualityClass(epObj)}) - - if currentManualSearchThreadActive: - searchThread = currentManualSearchThreadActive - searchstatus = 'searching' - if searchThread.success: - searchstatus = 'finished' - else: - searchstatus = 'searching' - episodes.append({'episode': searchThread.segment.episode, - 'episodeindexid': searchThread.segment.indexerid, - 'season' : searchThread.segment.season, - 'searchstatus' : searchstatus, - 'status' : statusStrings[searchThread.segment.status], - 'quality': self.getQualityClass(searchThread.segment)}) - - if finishedManualSearchThreadItems: - for searchThread in finishedManualSearchThreadItems: - if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem): - if str(searchThread.show.indexerid) == show and not [x for x in episodes if x['episodeindexid'] == searchThread.segment.indexerid]: - searchstatus = 'finished' - episodes.append({'episode': searchThread.segment.episode, - 'episodeindexid': searchThread.segment.indexerid, - 'season' : searchThread.segment.season, - 'searchstatus' : searchstatus, - 'status' : statusStrings[searchThread.segment.status], - 'quality': self.getQualityClass(searchThread.segment)}) - else: - ### These are only Failed Downloads/Retry SearchThreadItems.. lets loop through the segement/episodes - if str(searchThread.show.indexerid) == show: - for epObj in searchThread.segment: - if not [x for x in episodes if x['episodeindexid'] == epObj.indexerid]: - searchstatus = 'finished' - episodes.append({'episode': epObj.episode, - 'episodeindexid': epObj.indexerid, - 'season' : epObj.season, - 'searchstatus' : searchstatus, - 'status' : statusStrings[epObj.status], - 'quality': self.getQualityClass(epObj)}) - - return json.dumps({'show': show, 'episodes' : episodes}) - - #return json.dumps() - - def getQualityClass(self, ep_obj): - # return the correct json value - - # Find the quality class for the episode - quality_class = Quality.qualityStrings[Quality.UNKNOWN] - ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status) - for x in (SD, HD720p, HD1080p): - if ep_quality in Quality.splitQuality(x)[0]: - quality_class = qualityPresetStrings[x] - break - - return quality_class - - def searchEpisodeSubtitles(self, show=None, season=None, episode=None): - # retrieve the episode object and fail if we can't get one - ep_obj = _getEpisode(show, season, episode) - if isinstance(ep_obj, str): - return json.dumps({'result': 'failure'}) - - # try do download subtitles for that episode - previous_subtitles = set(subliminal.language.Language(x) for x in ep_obj.subtitles) - try: - ep_obj.subtitles = set(x.language for x in ep_obj.downloadSubtitles().values()[0]) - except: - return json.dumps({'result': 'failure'}) - - # return the correct json value - if previous_subtitles != ep_obj.subtitles: - status = 'New subtitles downloaded: %s' % ' '.join([ - "" + x.name + "" for x in - sorted(list(ep_obj.subtitles.difference(previous_subtitles)))]) - else: - status = 'No subtitles downloaded' - ui.notifications.message('Subtitles Search', status) - return json.dumps({'result': status, 'subtitles': ','.join(sorted([x.alpha2 for x in - ep_obj.subtitles.union(previous_subtitles)]))}) - - def setSceneNumbering(self, show, indexer, forSeason=None, forEpisode=None, forAbsolute=None, sceneSeason=None, - sceneEpisode=None, sceneAbsolute=None): - - # sanitize: - if forSeason in ['null', '']: forSeason = None - if forEpisode in ['null', '']: forEpisode = None - if forAbsolute in ['null', '']: forAbsolute = None - if sceneSeason in ['null', '']: sceneSeason = None - if sceneEpisode in ['null', '']: sceneEpisode = None - if sceneAbsolute in ['null', '']: sceneAbsolute = None - - showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) - - if showObj.is_anime: - result = { - 'success': True, - 'forAbsolute': forAbsolute, - } - else: - result = { - 'success': True, - 'forSeason': forSeason, - 'forEpisode': forEpisode, - } - - # retrieve the episode object and fail if we can't get one - if showObj.is_anime: - ep_obj = _getEpisode(show, absolute=forAbsolute) - else: - ep_obj = _getEpisode(show, forSeason, forEpisode) - - if isinstance(ep_obj, str): - result['success'] = False - result['errorMessage'] = ep_obj - elif showObj.is_anime: - logger.log(u"setAbsoluteSceneNumbering for %s from %s to %s" % - (show, forAbsolute, sceneAbsolute), logger.DEBUG) - - show = int(show) - indexer = int(indexer) - forAbsolute = int(forAbsolute) - if sceneAbsolute is not None: sceneAbsolute = int(sceneAbsolute) - - set_scene_numbering(show, indexer, absolute_number=forAbsolute, sceneAbsolute=sceneAbsolute) - else: - logger.log(u"setEpisodeSceneNumbering for %s from %sx%s to %sx%s" % - (show, forSeason, forEpisode, sceneSeason, sceneEpisode), logger.DEBUG) - - show = int(show) - indexer = int(indexer) - forSeason = int(forSeason) - forEpisode = int(forEpisode) - if sceneSeason is not None: sceneSeason = int(sceneSeason) - if sceneEpisode is not None: sceneEpisode = int(sceneEpisode) - - set_scene_numbering(show, indexer, season=forSeason, episode=forEpisode, sceneSeason=sceneSeason, - sceneEpisode=sceneEpisode) - - if showObj.is_anime: - sn = get_scene_absolute_numbering(show, indexer, forAbsolute) - if sn: - result['sceneAbsolute'] = sn - else: - result['sceneAbsolute'] = None - else: - sn = get_scene_numbering(show, indexer, forSeason, forEpisode) - if sn: - (result['sceneSeason'], result['sceneEpisode']) = sn - else: - (result['sceneSeason'], result['sceneEpisode']) = (None, None) - - return json.dumps(result) - - def retryEpisode(self, show, season, episode): - - # retrieve the episode object and fail if we can't get one - ep_obj = _getEpisode(show, season, episode) - if isinstance(ep_obj, str): - return json.dumps({'result': 'failure'}) - - # make a queue item for it and put it on the queue - ep_queue_item = search_queue.FailedQueueItem(ep_obj.show, [ep_obj]) - sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) # @UndefinedVariable - - if ep_queue_item.success: - return returnManualSearchResult(ep_queue_item) - if not ep_queue_item.started and ep_queue_item.success is None: - return json.dumps({'result': 'success'}) #I Actually want to call it queued, because the search hasnt been started yet! - if ep_queue_item.started and ep_queue_item.success is None: - return json.dumps({'result': 'success'}) - else: - return json.dumps({'result': 'failure'}) - - @staticmethod - def fetch_releasegroups(show_name): - - if helpers.set_up_anidb_connection(): - try: - anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=show_name) - groups = anime.get_groups() - except Exception, e: - logger.log(u'exception msg: ' + str(e), logger.DEBUG) - return json.dumps({'result': 'fail', 'resp': 'connect'}) - - return json.dumps({'result': 'success', 'groups': groups}) - - return json.dumps({'result': 'fail', 'resp': 'init'}) - - -class UI(MainHandler): - def add_message(self): - ui.notifications.message('Test 1', 'This is test number 1') - ui.notifications.error('Test 2', 'This is test number 2') - - return "ok" - - def get_messages(self): - messages = {} - cur_notification_num = 1 - for cur_notification in ui.notifications.get_notifications(self.request.remote_ip): - messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title, - 'message': cur_notification.message, - 'type': cur_notification.type} - cur_notification_num += 1 - - return json.dumps(messages) + return t.respond() \ No newline at end of file diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index 2d58f2e3..67805582 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -1,11 +1,8 @@ import os -import socket -import time import threading import sys import sickbeard import webserve -import browser import webapi from sickbeard import logger @@ -90,7 +87,7 @@ class WebServer(threading.Thread): # Main Handler self.app.add_handlers('.*$', [ - (r'%s/api/builder(/?)(.*)' % self.options['web_root'], webapi.ApiBuilder), + (r'%s/api/builder(/?)(.*)' % self.options['web_root'], webserve.ApiBuilder), (r'%s/api(/?.*)' % self.options['web_root'], webapi.Api), (r'%s/config/general(/?.*)' % self.options['web_root'], webserve.ConfigGeneral), (r'%s/config/search(/?.*)' % self.options['web_root'], webserve.ConfigSearch), @@ -109,7 +106,7 @@ class WebServer(threading.Thread): (r'%s/manage/manageSearches(/?.*)' % self.options['web_root'], webserve.ManageSearches), (r'%s/manage/(/?.*)' % self.options['web_root'], webserve.Manage), (r'%s/ui(/?.*)' % self.options['web_root'], webserve.UI), - (r'%s/browser(/?.*)' % self.options['web_root'], browser.WebFileBrowser), + (r'%s/browser(/?.*)' % self.options['web_root'], webserve.WebFileBrowser), (r'%s(/?.*)' % self.options['web_root'], webserve.MainHandler), ])