2014-06-11 08:34:28 +00:00
|
|
|
import os
|
2014-07-02 18:51:14 +00:00
|
|
|
import threading
|
|
|
|
import sys
|
2014-03-10 05:18:05 +00:00
|
|
|
import sickbeard
|
2014-06-11 08:34:28 +00:00
|
|
|
import webserve
|
2014-06-15 21:45:09 +00:00
|
|
|
import webapi
|
2014-06-15 11:08:41 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
from sickbeard import logger
|
2018-03-29 16:23:33 +00:00
|
|
|
from sickbeard.helpers import create_https_certificates, re_valid_hostname
|
2015-08-07 23:53:30 +00:00
|
|
|
from tornado.web import Application
|
2014-06-16 12:19:07 +00:00
|
|
|
from tornado.ioloop import IOLoop
|
2014-06-11 08:34:28 +00:00
|
|
|
|
2014-07-09 18:41:04 +00:00
|
|
|
|
2014-11-13 05:36:47 +00:00
|
|
|
class WebServer(threading.Thread):
|
2018-03-29 16:23:33 +00:00
|
|
|
def __init__(self, options=None):
|
2014-07-02 18:51:14 +00:00
|
|
|
threading.Thread.__init__(self)
|
|
|
|
self.daemon = True
|
|
|
|
self.alive = True
|
2015-02-06 11:39:10 +00:00
|
|
|
self.name = 'TORNADO'
|
2018-03-27 16:13:58 +00:00
|
|
|
self.io_loop = None
|
2017-01-26 00:32:39 +00:00
|
|
|
self.server = None
|
2014-07-02 18:51:14 +00:00
|
|
|
|
2018-03-29 16:23:33 +00:00
|
|
|
self.options = options or {}
|
2014-07-02 18:51:14 +00:00
|
|
|
self.options.setdefault('port', 8081)
|
|
|
|
self.options.setdefault('host', '0.0.0.0')
|
|
|
|
self.options.setdefault('log_dir', None)
|
|
|
|
self.options.setdefault('username', '')
|
|
|
|
self.options.setdefault('password', '')
|
2014-07-09 18:41:04 +00:00
|
|
|
self.options.setdefault('web_root', None)
|
2014-07-02 18:51:14 +00:00
|
|
|
assert isinstance(self.options['port'], int)
|
|
|
|
assert 'data_root' in self.options
|
|
|
|
|
2014-07-09 18:41:04 +00:00
|
|
|
# web root
|
2017-01-26 00:32:39 +00:00
|
|
|
self.options['web_root'] = ('/' + self.options['web_root'].lstrip('/')) if self.options['web_root'] else ''
|
2014-07-09 18:41:04 +00:00
|
|
|
|
2014-07-02 18:51:14 +00:00
|
|
|
# tornado setup
|
|
|
|
self.enable_https = self.options['enable_https']
|
|
|
|
self.https_cert = self.options['https_cert']
|
|
|
|
self.https_key = self.options['https_key']
|
|
|
|
|
|
|
|
if self.enable_https:
|
2018-03-29 16:23:33 +00:00
|
|
|
make_cert = False
|
|
|
|
update_cfg = False
|
|
|
|
for (attr, ext) in [('https_cert', '.crt'), ('https_key', '.key')]:
|
|
|
|
ssl_path = getattr(self, attr, None)
|
|
|
|
if ssl_path and not os.path.isfile(ssl_path):
|
|
|
|
if not ssl_path.endswith(ext):
|
|
|
|
setattr(self, attr, os.path.join(ssl_path, 'server%s' % ext))
|
|
|
|
setattr(sickbeard, attr.upper(), 'server%s' % ext)
|
|
|
|
make_cert = True
|
|
|
|
|
2014-07-02 18:51:14 +00:00
|
|
|
# If either the HTTPS certificate or key do not exist, make some self-signed ones.
|
2018-03-29 16:23:33 +00:00
|
|
|
if make_cert:
|
2014-07-02 18:51:14 +00:00
|
|
|
if not create_https_certificates(self.https_cert, self.https_key):
|
2015-02-06 11:39:10 +00:00
|
|
|
logger.log(u'Unable to create CERT/KEY files, disabling HTTPS')
|
2018-03-29 16:23:33 +00:00
|
|
|
update_cfg |= False is not sickbeard.ENABLE_HTTPS
|
2014-07-02 18:51:14 +00:00
|
|
|
sickbeard.ENABLE_HTTPS = False
|
2014-07-06 00:57:43 +00:00
|
|
|
self.enable_https = False
|
2018-03-29 16:23:33 +00:00
|
|
|
else:
|
|
|
|
update_cfg = True
|
2014-07-02 18:51:14 +00:00
|
|
|
|
2018-03-29 16:23:33 +00:00
|
|
|
if not (os.path.isfile(self.https_cert) and os.path.isfile(self.https_key)):
|
2015-02-06 11:39:10 +00:00
|
|
|
logger.log(u'Disabled HTTPS because of missing CERT and KEY files', logger.WARNING)
|
2018-03-29 16:23:33 +00:00
|
|
|
update_cfg |= False is not sickbeard.ENABLE_HTTPS
|
2014-03-10 05:18:05 +00:00
|
|
|
sickbeard.ENABLE_HTTPS = False
|
2014-07-06 00:57:43 +00:00
|
|
|
self.enable_https = False
|
2014-03-10 05:18:05 +00:00
|
|
|
|
2018-03-29 16:23:33 +00:00
|
|
|
if update_cfg:
|
|
|
|
sickbeard.save_config()
|
|
|
|
|
2014-07-02 18:51:14 +00:00
|
|
|
# Load the app
|
|
|
|
self.app = Application([],
|
2018-03-27 16:13:58 +00:00
|
|
|
debug=False,
|
2018-03-29 16:23:33 +00:00
|
|
|
serve_traceback=True,
|
2015-08-07 23:53:30 +00:00
|
|
|
autoreload=False,
|
2018-03-29 16:23:33 +00:00
|
|
|
compress_response=True,
|
2015-08-07 23:53:30 +00:00
|
|
|
cookie_secret=sickbeard.COOKIE_SECRET,
|
2018-03-29 16:23:33 +00:00
|
|
|
xsrf_cookies=True,
|
2017-01-26 00:32:39 +00:00
|
|
|
login_url='%s/login/' % self.options['web_root'])
|
2014-07-02 18:51:14 +00:00
|
|
|
|
2018-03-29 16:23:33 +00:00
|
|
|
re_host_pattern = re_valid_hostname()
|
|
|
|
|
2015-02-06 11:39:10 +00:00
|
|
|
# webui login/logout handlers
|
2018-03-29 16:23:33 +00:00
|
|
|
self.app.add_handlers(re_host_pattern, [
|
2015-02-06 11:39:10 +00:00
|
|
|
(r'%s/login(/?)' % self.options['web_root'], webserve.LoginHandler),
|
|
|
|
(r'%s/logout(/?)' % self.options['web_root'], webserve.LogoutHandler),
|
|
|
|
])
|
|
|
|
|
|
|
|
# Web calendar handler (Needed because option Unprotected calendar)
|
2018-03-29 16:23:33 +00:00
|
|
|
self.app.add_handlers(re_host_pattern, [
|
2015-02-06 11:39:10 +00:00
|
|
|
(r'%s/calendar' % self.options['web_root'], webserve.CalendarHandler),
|
|
|
|
])
|
|
|
|
|
|
|
|
# Static File Handlers
|
2018-03-29 16:23:33 +00:00
|
|
|
self.app.add_handlers(re_host_pattern, [
|
2015-02-06 11:39:10 +00:00
|
|
|
# favicon
|
2015-08-07 23:53:30 +00:00
|
|
|
(r'%s/(favicon\.ico)' % self.options['web_root'], webserve.BaseStaticFileHandler,
|
2015-02-06 11:39:10 +00:00
|
|
|
{'path': os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}),
|
|
|
|
|
|
|
|
# images
|
2015-08-07 23:53:30 +00:00
|
|
|
(r'%s/images/(.*)' % self.options['web_root'], webserve.BaseStaticFileHandler,
|
2015-02-06 11:39:10 +00:00
|
|
|
{'path': os.path.join(self.options['data_root'], 'images')}),
|
|
|
|
|
|
|
|
# cached images
|
2015-08-07 23:53:30 +00:00
|
|
|
(r'%s/cache/images/(.*)' % self.options['web_root'], webserve.BaseStaticFileHandler,
|
2015-02-06 11:39:10 +00:00
|
|
|
{'path': os.path.join(sickbeard.CACHE_DIR, 'images')}),
|
|
|
|
|
|
|
|
# css
|
2015-08-07 23:53:30 +00:00
|
|
|
(r'%s/css/(.*)' % self.options['web_root'], webserve.BaseStaticFileHandler,
|
2015-02-06 11:39:10 +00:00
|
|
|
{'path': os.path.join(self.options['data_root'], 'css')}),
|
|
|
|
|
|
|
|
# javascript
|
2015-08-07 23:53:30 +00:00
|
|
|
(r'%s/js/(.*)' % self.options['web_root'], webserve.BaseStaticFileHandler,
|
2015-02-06 11:39:10 +00:00
|
|
|
{'path': os.path.join(self.options['data_root'], 'js')}),
|
Add choose to delete watched episodes from a list of played media at Kodi, Emby, and/or Plex.
Add episode watched state system that integrates with Kodi, Plex, and/or Emby, instructions at Shows/History/Layout/"Watched".
Add installable SickGear Kodi repository containing addon "SickGear Watched State Updater".
Change add Emby setting for watched state scheduler at Config/Notifications/Emby/"Update watched interval".
Change add Plex setting for watched state scheduler at Config/Notifications/Plex/"Update watched interval".
Add API cmd=sg.updatewatchedstate, instructions for use are linked to in layout "Watched" at /history.
Change history page table filter input values are saved across page refreshes.
Change history page table filter inputs, accept values like "dvd or web" to only display both.
Change history page table filter inputs, press 'ESC' key inside a filter input to reset it.
Add provider activity stats to Shows/History/Layout/ drop down.
Change move provider failures table from Manage/Media Search to Shows/History/Layout/Provider fails.
Change sort provider failures by most recent failure, and with paused providers at the top.
Change remove table form non-testing version 20007, that was reassigned.
2018-03-06 01:18:08 +00:00
|
|
|
|
|
|
|
(r'%s/kodi/(.*)' % self.options['web_root'], webserve.RepoHandler,
|
|
|
|
{'path': os.path.join(sickbeard.CACHE_DIR, 'clients', 'kodi'),
|
|
|
|
'default_filename': 'index.html'}),
|
2014-07-02 18:51:14 +00:00
|
|
|
])
|
|
|
|
|
2017-02-01 23:58:38 +00:00
|
|
|
# Main Handler
|
2018-03-29 16:23:33 +00:00
|
|
|
self.app.add_handlers(re_host_pattern, [
|
2017-02-01 23:58:38 +00:00
|
|
|
(r'%s/api/builder(/?)(.*)' % self.options['web_root'], webserve.ApiBuilder),
|
|
|
|
(r'%s/api(/?.*)' % self.options['web_root'], webapi.Api),
|
|
|
|
(r'%s/imagecache(/?.*)' % self.options['web_root'], webserve.CachedImages),
|
|
|
|
(r'%s/cache(/?.*)' % self.options['web_root'], webserve.Cache),
|
|
|
|
(r'%s/config/general(/?.*)' % self.options['web_root'], webserve.ConfigGeneral),
|
|
|
|
(r'%s/config/search(/?.*)' % self.options['web_root'], webserve.ConfigSearch),
|
|
|
|
(r'%s/config/providers(/?.*)' % self.options['web_root'], webserve.ConfigProviders),
|
|
|
|
(r'%s/config/subtitles(/?.*)' % self.options['web_root'], webserve.ConfigSubtitles),
|
|
|
|
(r'%s/config/postProcessing(/?.*)' % self.options['web_root'], webserve.ConfigPostProcessing),
|
|
|
|
(r'%s/config/notifications(/?.*)' % self.options['web_root'], webserve.ConfigNotifications),
|
|
|
|
(r'%s/config/anime(/?.*)' % self.options['web_root'], webserve.ConfigAnime),
|
|
|
|
(r'%s/config(/?.*)' % self.options['web_root'], webserve.Config),
|
|
|
|
(r'%s/errorlogs(/?.*)' % self.options['web_root'], webserve.ErrorLogs),
|
|
|
|
(r'%s/history(/?.*)' % self.options['web_root'], webserve.History),
|
|
|
|
(r'%s/home/is_alive(/?.*)' % self.options['web_root'], webserve.IsAliveHandler),
|
|
|
|
(r'%s/home/addShows(/?.*)' % self.options['web_root'], webserve.NewHomeAddShows),
|
|
|
|
(r'%s/home/postprocess(/?.*)' % self.options['web_root'], webserve.HomePostProcess),
|
|
|
|
(r'%s/home(/?.*)' % self.options['web_root'], webserve.Home),
|
|
|
|
(r'%s/manage/manageSearches(/?.*)' % self.options['web_root'], webserve.ManageSearches),
|
|
|
|
(r'%s/manage/showProcesses(/?.*)' % self.options['web_root'], webserve.showProcesses),
|
|
|
|
(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'], webserve.WebFileBrowser),
|
2018-04-03 12:05:37 +00:00
|
|
|
(r'%s(/?update_watched_state_kodi/?)' % self.options['web_root'], webserve.NoXSRFHandler),
|
2017-02-01 23:58:38 +00:00
|
|
|
(r'%s(/?.*)' % self.options['web_root'], webserve.MainHandler),
|
|
|
|
])
|
|
|
|
|
2014-07-02 18:51:14 +00:00
|
|
|
def run(self):
|
2017-01-26 00:32:39 +00:00
|
|
|
protocol, ssl_options = (('http', None),
|
|
|
|
('https', {'certfile': self.https_cert, 'keyfile': self.https_key}))[self.enable_https]
|
2014-07-02 18:51:14 +00:00
|
|
|
|
2015-02-06 11:39:10 +00:00
|
|
|
logger.log(u'Starting SickGear on ' + protocol + '://' + str(self.options['host']) + ':' + str(
|
|
|
|
self.options['port']) + '/')
|
2014-07-02 18:51:14 +00:00
|
|
|
|
|
|
|
try:
|
2017-01-26 00:32:39 +00:00
|
|
|
self.server = self.app.listen(self.options['port'], self.options['host'], ssl_options=ssl_options,
|
|
|
|
xheaders=sickbeard.HANDLE_REVERSE_PROXY, protocol=protocol)
|
|
|
|
except (StandardError, Exception):
|
2014-07-02 18:51:14 +00:00
|
|
|
etype, evalue, etb = sys.exc_info()
|
2014-07-09 18:41:04 +00:00
|
|
|
logger.log(
|
2015-02-06 11:39:10 +00:00
|
|
|
'Could not start webserver on %s. Excpeption: %s, Error: %s' % (self.options['port'], etype, evalue),
|
2014-07-09 18:41:04 +00:00
|
|
|
logger.ERROR)
|
2014-07-02 18:51:14 +00:00
|
|
|
return
|
|
|
|
|
2018-03-27 16:13:58 +00:00
|
|
|
self.io_loop = IOLoop.current()
|
|
|
|
|
2014-07-02 18:51:14 +00:00
|
|
|
try:
|
|
|
|
self.io_loop.start()
|
|
|
|
self.io_loop.close(True)
|
2014-07-15 02:00:53 +00:00
|
|
|
except (IOError, ValueError):
|
2015-02-06 11:39:10 +00:00
|
|
|
# Ignore errors like 'ValueError: I/O operation on closed kqueue fd'. These might be thrown during a reload.
|
2014-07-02 18:51:14 +00:00
|
|
|
pass
|
|
|
|
|
2018-03-29 16:23:33 +00:00
|
|
|
def shut_down(self):
|
2014-07-02 18:51:14 +00:00
|
|
|
self.alive = False
|
2018-03-27 16:13:58 +00:00
|
|
|
if None is not self.io_loop:
|
|
|
|
self.io_loop.stop()
|