diff --git a/SickBeard.py b/SickBeard.py index 73da7602..fed90122 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -385,6 +385,7 @@ def main(): # start IOLoop IOLoop.current().start() + sickbeard.saveAndShutdown() return if __name__ == "__main__": diff --git a/gui/slick/js/restart.js b/gui/slick/js/restart.js index b98b0a8d..edc4074f 100644 --- a/gui/slick/js/restart.js +++ b/gui/slick/js/restart.js @@ -37,7 +37,7 @@ function is_alive() { $('#restart_loading').hide(); $('#restart_success').show(); $('#refresh_message').show(); - window.location = sb_base_url+'/home'; + window.location = sb_base_url+'/home/'; } } }, 'jsonp'); @@ -66,7 +66,7 @@ $(document).ready(function() $('#restart_success').show(); $('#refresh_message').show(); }, 3000); - setTimeout("window.location = sb_base_url+'/home'", 5000); + setTimeout("window.location = sb_base_url+'/home/'", 5000); } } diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 863cf05d..2226a0f6 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -105,6 +105,7 @@ CUR_COMMIT_HASH = None INIT_LOCK = Lock() __INITIALIZED__ = False started = False +restarted = False ACTUAL_LOG_DIR = None LOG_DIR = None @@ -1292,16 +1293,7 @@ def saveAll(): logger.log(u"Saving config file to disk") save_config() -def saveAndShutdown(restart=False): - if not restart: - logger.log('Shutting down tornado') - try: - IOLoop.current().stop() - except RuntimeError: - pass - except: - logger.log('Failed shutting down the server: %s' % traceback.format_exc(), logger.ERROR) - +def saveAndShutdown(): halt() saveAll() @@ -1309,7 +1301,7 @@ def saveAndShutdown(restart=False): logger.log(u"Removing pidfile " + str(PIDFILE)) remove_pid_file(PIDFILE) - if restart: + if restarted: install_type = versionCheckScheduler.action.install_type popen_list = [] @@ -1352,18 +1344,20 @@ def invoke_restart(soft=True): def invoke_shutdown(): - invoke_command(saveAndShutdown) + invoke_command(webserveInit.shutdown) def restart(soft=True): + global restarted + if soft: halt() saveAll() logger.log(u"Re-initializing all data") initialize() - else: - saveAndShutdown(restart=True) + restarted=True + webserveInit.shutdown() def save_config(): diff --git a/sickbeard/browser.py b/sickbeard/browser.py index 94cf3eb8..1c790ff3 100644 --- a/sickbeard/browser.py +++ b/sickbeard/browser.py @@ -102,10 +102,10 @@ def foldersAtPath(path, includeParent=False): class WebFileBrowser(RequestHandler): def index(self, path=''): - HTTPHeaders()['Content-Type'] = "application/json" + self.set_header("Content-Type", "application/json") return self.finish(json.dumps(foldersAtPath(path, True))) def complete(self, term): - HTTPHeaders()['Content-Type'] = "application/json" + self.set_header("Content-Type", "application/json") paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term)) if 'path' in entry] return self.finish(json.dumps(paths)) diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py index 50dcfc07..4f4dddaf 100644 --- a/sickbeard/webapi.py +++ b/sickbeard/webapi.py @@ -157,14 +157,14 @@ class Api(webserve.IndexHandler): else: t.apikey = "api key not generated" - return self.finish(webserve._munge(t)) + return webserve._munge(t) def _out_as_json(self, dict): """ set cherrypy response to json """ - self.h["content-type"] = "application/json;charset=UTF-8" + self.set_header("Content-Type", "application/json") try: out = json.dumps(dict, indent=self.intent, sort_keys=True) - callback = self.h.get('callback' ,None) or self.h.get('jsonp' ,None) + callback = self.request.headers.get('callback', None) or self.request.headers.get('jsonp', None) if callback != None: out = callback + '(' + out + ');' # wrap with JSONP call if requested except Exception, e: # if we fail to generate the output fake an error @@ -175,7 +175,7 @@ class Api(webserve.IndexHandler): def _grand_access(self, realKey, args, kwargs): """ validate api key and log result """ - remoteIp = sickbeard.REMOTE_IP + remoteIp = self.request.remote_ip apiKey = kwargs.get("apikey", None) if not apiKey: if args: # if we have keyless vars we assume first one is the api key, always ! @@ -1506,7 +1506,7 @@ class CMD_SickBeardPing(ApiCall): def run(self): """ check to see if sickbeard is running """ - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") if sickbeard.started: return _responds(RESULT_SUCCESS, {"pid": sickbeard.PID}, "Pong") else: diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 7690fb6b..36086585 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -28,12 +28,8 @@ import re import threading import datetime import random - -from Cheetah.Template import Template import sys -from tornado import gen -from tornado.httputil import HTTPHeaders -from tornado.web import RequestHandler, HTTPError, asynchronous, authenticated + import sickbeard from sickbeard import config, sab @@ -80,13 +76,19 @@ except ImportError: from lib import adba -#def _handle_reverse_proxy(): +from Cheetah.Template import Template +from tornado import gen +from tornado.web import RequestHandler, HTTPError, asynchronous, authenticated + +# def _handle_reverse_proxy(): # if sickbeard.HANDLE_REVERSE_PROXY: # cherrypy.lib.cptools.proxy() # cherrypy.tools.handle_reverse_proxy = cherrypy.Tool('before_handler', _handle_reverse_proxy) +req_headers = None + def require_basic_auth(handler_class): def wrap_execute(handler_execute): def require_basic_auth(handler, kwargs): @@ -115,15 +117,18 @@ def require_basic_auth(handler_class): handler.clear_cookie("user") get_auth() + def _execute(self, transforms, *args, **kwargs): if not require_basic_auth(self, kwargs): return False return handler_execute(self, transforms, *args, **kwargs) + return _execute handler_class._execute = wrap_execute(handler_class._execute) return handler_class + class RedirectHandler(RequestHandler): """Redirects the client to the given URL for all GET requests. @@ -133,47 +138,25 @@ class RedirectHandler(RequestHandler): (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), ]) """ + def get(self, path): self.redirect(path, permanent=True) + @require_basic_auth class LoginHandler(RedirectHandler): def get(self, path): self.redirect(self.get_argument("next", u"/")) + @require_basic_auth class IndexHandler(RedirectHandler): def __init__(self, application, request, **kwargs): super(IndexHandler, self).__init__(application, request, **kwargs) + global req_headers - self.remote_ip = sickbeard.REMOTE_IP = self.request.headers.get('X-Forwarded-For', - self.request.headers.get('X-Real-Ip', self.request.remote_ip)) - - self.h = HTTPHeaders({"content-type": "text/html"}) - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" - self.h['Content-Type'] = 'text/javascript' - self.h['Access-Control-Allow-Origin'] = '*' - self.h['Access-Control-Allow-Headers'] = 'x-requested-with' - - if 'Host' in self.h: - if self.h['Host'][0] == '[': - self.sbHost = re.match("^\[.*\]", self.h['Host'], re.X | re.M | re.S).group(0) - else: - self.sbHost = re.match("^[^:]+", self.h['Host'], re.X | re.M | re.S).group(0) - - if sickbeard.NZBS and sickbeard.NZBS_UID and sickbeard.NZBS_HASH: - logger.log(u"NZBs.org has been replaced, please check the config to configure the new provider!", - logger.ERROR) - ui.notifications.error("NZBs.org Config Update", - "NZBs.org has a new site. Please update your config with the api key from http://nzbs.org and then disable the old NZBs.org provider.") - - if "X-Forwarded-Host" in self.h: - self.sbHost = self.h['X-Forwarded-Host'] - if "X-Forwarded-Port" in self.h: - sbHttpPort = self.h['X-Forwarded-Port'] - self.sbHttpsPort = sbHttpPort - if "X-Forwarded-Proto" in self.h: - self.sbHttpsEnabled = True if self.h['X-Forwarded-Proto'] == 'https' else False + sickbeard.REMOTE_IP = self.request.remote_ip + req_headers = self.request.headers def delist_arguments(self, args): """ @@ -200,8 +183,10 @@ class IndexHandler(RedirectHandler): return inspect.isclass(c) and c.__module__ == pred.__module__ try: - klass = [cls[1] for cls in inspect.getmembers(sys.modules[__name__], pred) + [(self.__class__.__name__, self.__class__)] if - cls[0].lower() == method.lower() or method in cls[1].__dict__.keys()][0](self.application, self.request) + klass = [cls[1] for cls in + inspect.getmembers(sys.modules[__name__], pred) + [(self.__class__.__name__, self.__class__)] if + cls[0].lower() == method.lower() or method in cls[1].__dict__.keys()][0](self.application, + self.request) except: klass = None @@ -416,7 +401,7 @@ class IndexHandler(RedirectHandler): """ Provides a subscribeable URL for iCal subscriptions """ - logger.log(u"Receiving iCal request from %s" % self.request.remote.ip) + logger.log(u"Receiving iCal request from %s" % self.request.remote_ip) poster_url = self.request.url().replace('ical', '') @@ -479,18 +464,33 @@ class IndexHandler(RedirectHandler): browser = WebFileBrowser + class PageTemplate(Template): def __init__(self, *args, **KWs): KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/", KWs['file']) super(PageTemplate, self).__init__(*args, **KWs) + global req_headers + self.sbRoot = sickbeard.WEB_ROOT self.sbHttpPort = sickbeard.WEB_PORT self.sbHttpsPort = sickbeard.WEB_PORT - self.sbHost = sickbeard.WEB_HOST self.sbHttpsEnabled = sickbeard.ENABLE_HTTPS self.sbHandleReverseProxy = sickbeard.HANDLE_REVERSE_PROXY + if req_headers['Host'][0] == '[': + self.sbHost = re.match("^\[.*\]", req_headers['Host'], re.X | re.M | re.S).group(0) + else: + self.sbHost = re.match("^[^:]+", req_headers['Host'], re.X | re.M | re.S).group(0) + + if "X-Forwarded-Host" in req_headers: + self.sbHost = req_headers['X-Forwarded-Host'] + if "X-Forwarded-Port" in req_headers: + sbHttpPort = req_headers['X-Forwarded-Port'] + self.sbHttpsPort = sbHttpPort + if "X-Forwarded-Proto" in req_headers: + self.sbHttpsEnabled = True if req_headers['X-Forwarded-Proto'] == 'https' else False + logPageTitle = 'Logs & Errors' if len(classes.ErrorViewer.errors): logPageTitle += ' (' + str(len(classes.ErrorViewer.errors)) + ')' @@ -1070,8 +1070,8 @@ class Manage(IndexHandler): exceptions_list = [] curErrors += self.editShow(curShow, new_show_dir, anyQualities, bestQualities, exceptions_list, - new_flatten_folders, new_paused, subtitles=new_subtitles, anime=new_anime, - scene=new_scene, directCall=True) + new_flatten_folders, new_paused, subtitles=new_subtitles, anime=new_anime, + scene=new_scene, directCall=True) if curErrors: logger.log(u"Errors: " + str(curErrors), logger.ERROR) @@ -1346,6 +1346,7 @@ ConfigMenu = [ {'title': 'Anime', 'path': 'config/anime/'}, ] + class ConfigGeneral(IndexHandler): def index(self, *args, **kwargs): @@ -2432,8 +2433,8 @@ class ConfigAnime(IndexHandler): self.redirect("/config/anime/") -class Config(IndexHandler): +class Config(IndexHandler): def index(self, *args, **kwargs): t = PageTemplate(file="config.tmpl") t.submenu = ConfigMenu @@ -2449,6 +2450,7 @@ class Config(IndexHandler): notifications = ConfigNotifications anime = ConfigAnime + def haveXBMC(): return sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY @@ -2976,6 +2978,11 @@ class Home(IndexHandler): 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: return callback + '(' + json.dumps({"msg": str(sickbeard.PID)}) + ');' else: @@ -3031,7 +3038,7 @@ class Home(IndexHandler): def testGrowl(self, host=None, password=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") host = config.clean_host(host, default_port=23053) @@ -3048,7 +3055,7 @@ class Home(IndexHandler): def testProwl(self, prowl_api=None, prowl_priority=0): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.prowl_notifier.test_notify(prowl_api, prowl_priority) if result: @@ -3058,7 +3065,7 @@ class Home(IndexHandler): def testBoxcar(self, username=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.boxcar_notifier.test_notify(username) if result: @@ -3068,7 +3075,7 @@ class Home(IndexHandler): def testBoxcar2(self, accesstoken=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.boxcar2_notifier.test_notify(accesstoken) if result: @@ -3078,7 +3085,7 @@ class Home(IndexHandler): def testPushover(self, userKey=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.pushover_notifier.test_notify(userKey) if result: @@ -3088,13 +3095,13 @@ class Home(IndexHandler): def twitterStep1(self, *args, **kwargs): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") return notifiers.twitter_notifier._get_authorization() def twitterStep2(self, key): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + 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)) @@ -3105,7 +3112,7 @@ class Home(IndexHandler): def testTwitter(self, *args, **kwargs): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.twitter_notifier.test_notify() if result: @@ -3115,7 +3122,7 @@ class Home(IndexHandler): def testXBMC(self, host=None, username=None, password=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") host = config.clean_hosts(host) finalResult = '' @@ -3131,7 +3138,7 @@ class Home(IndexHandler): def testPLEX(self, host=None, username=None, password=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") finalResult = '' for curHost in [x.strip() for x in host.split(",")]: @@ -3146,7 +3153,7 @@ class Home(IndexHandler): def testLibnotify(self, *args, **kwargs): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + 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" @@ -3155,7 +3162,7 @@ class Home(IndexHandler): def testNMJ(self, host=None, database=None, mount=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + 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) @@ -3166,7 +3173,7 @@ class Home(IndexHandler): def settingsNMJ(self, host=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + 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)) @@ -3178,7 +3185,7 @@ class Home(IndexHandler): def testNMJv2(self, host=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + 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)) @@ -3189,7 +3196,7 @@ class Home(IndexHandler): def settingsNMJv2(self, host=None, dbloc=None, instance=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + 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) @@ -3202,7 +3209,7 @@ class Home(IndexHandler): def testTrakt(self, api=None, username=None, password=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.trakt_notifier.test_notify(api, username, password) if result: @@ -3212,7 +3219,7 @@ class Home(IndexHandler): def loadShowNotifyLists(self, *args, **kwargs): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") with db.DBConnection() as myDB: rows = myDB.select("SELECT show_id, show_name, notify_list FROM tv_shows ORDER BY show_name ASC") @@ -3227,7 +3234,7 @@ class Home(IndexHandler): def testEmail(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + 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): @@ -3237,7 +3244,7 @@ class Home(IndexHandler): def testNMA(self, nma_api=None, nma_priority=0): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.nma_notifier.test_notify(nma_api, nma_priority) if result: @@ -3247,7 +3254,7 @@ class Home(IndexHandler): def testPushalot(self, authorizationToken=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.pushalot_notifier.test_notify(authorizationToken) if result: @@ -3257,7 +3264,7 @@ class Home(IndexHandler): def testPushbullet(self, api=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.pushbullet_notifier.test_notify(api) if result: @@ -3267,7 +3274,7 @@ class Home(IndexHandler): def getPushbulletDevices(self, api=None): - self.h['Cache-Control'] = "max-age=0,no-cache,no-store" + self.set_header('Cache-Control', "max-age=0,no-cache,no-store") result = notifiers.pushbullet_notifier.get_devices(api) if result: diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index 48281f6a..8f070cda 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -1,5 +1,7 @@ import os +import traceback import sickbeard +from tornado.ioloop import IOLoop import webserve import webapi @@ -93,9 +95,10 @@ def initWebServer(options={}): # Load the app app = Application([], - debug=False, + debug=True, gzip=True, autoreload=True, + xheaders=False, cookie_secret='61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=', login_url='/login' ) @@ -134,4 +137,13 @@ def initWebServer(options={}): logger.log(u"Starting SickRage on " + protocol + "://" + str(options['host']) + ":" + str( options['port']) + "/") - server.listen(options['port'], options['host']) \ No newline at end of file + server.listen(options['port'], options['host']) + +def shutdown(): + logger.log('Shutting down tornado') + try: + IOLoop.current().stop() + except RuntimeError: + pass + except: + logger.log('Failed shutting down the server: %s' % traceback.format_exc(), logger.ERROR) \ No newline at end of file