Browse Source

Change sickgear.py can now be run as start up instead of SickBeard.py.

Change remove duplicate code in SickBeard as sickgear.py can now be executed directly.
Change refactor startup functions to prevent possible latency issues with systemd.
Add startup loading page.
Refactor loading message list to have optional progress (progress dict key != -1).
Add upgrade messages for sickbeard, cache, and failed db upgrade processes to loading page.
Add rollback message (as preparation that rollback module can set progress messages for it).
Add set .load_msg to rollback module class.
Change restart to use loading page.
Change add is_alive to loading webserver and set started var for is_alive after webserver is started.
pull/1200/head
Prinz23 6 years ago
committed by JackDandy
parent
commit
bbf6622625
  1. 5
      CHANGES.md
  2. 654
      SickBeard.py
  3. 61
      gui/slick/interfaces/default/loading.tmpl
  4. 70
      gui/slick/js/loadingStartup.js
  5. 6
      init-scripts/init.debian
  6. 2
      init-scripts/init.fedora
  7. 2
      init-scripts/init.freebsd
  8. 4
      init-scripts/init.gentoo
  9. 2
      init-scripts/init.solaris11
  10. 8
      init-scripts/init.systemd
  11. 6
      init-scripts/init.ubuntu
  12. 1894
      sickbeard/__init__.py
  13. 35
      sickbeard/classes.py
  14. 144
      sickbeard/databases/mainDB.py
  15. 15
      sickbeard/db.py
  16. 12
      sickbeard/logger.py
  17. 8
      sickbeard/webapi.py
  18. 43
      sickbeard/webserve.py
  19. 85
      sickbeard/webserveInit.py
  20. 139
      sickgear.py
  21. 4
      tests/compatibility_tests.py

5
CHANGES.md

@ -27,6 +27,11 @@
* Update urllib3 release 1.24.3 (324e47a) to 1.25.2 (49eea80)
* Update win_inet_pton 1.0.1 (934a852) to 1.1.0 (57e3558)
* Update xmltodict library 0.11.0 (79ac9a4) to 0.12.0 (f3ab7e1)
* Change sickgear.py can now be run as start up instead of SickBeard.py
* Change refactor startup functions to prevent possible latency issues with systemd
* Add startup loading page
* Change restart to use loading page
* Add upgrade messages for sickbeard, cache, and failed db upgrade processes to loading page
[develop changelog]

654
SickBeard.py

@ -1,6 +1,4 @@
#!/usr/bin/env python2
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of SickGear.
#
@ -17,644 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
# Check needed software dependencies to nudge users to fix their setup
from __future__ import print_function
from __future__ import with_statement
import datetime
import errno
import getopt
import locale
import os
import signal
import sys
import shutil
import subprocess
import time
import threading
import warnings
warnings.filterwarnings('ignore', module=r'.*fuzzywuzzy.*')
warnings.filterwarnings('ignore', module=r'.*Cheetah.*')
warnings.filterwarnings('ignore', module=r'.*connectionpool.*', message='.*certificate verification.*')
warnings.filterwarnings('ignore', module=r'.*ssl_.*', message='.*SSLContext object.*')
warnings.filterwarnings('ignore', module=r'.*zoneinfo.*', message='.*file or directory.*')
if not (2, 7, 9) <= sys.version_info < (3, 0):
print('Python %s.%s.%s detected.' % sys.version_info[:3])
print('Sorry, SickGear requires Python 2.7.9 or higher. Python 3 is not supported.')
sys.exit(1)
try:
import _cleaner
except (StandardError, Exception):
pass
try:
import Cheetah
if Cheetah.Version[0] < '2':
raise ValueError
except ValueError:
print('Sorry, requires Python module Cheetah 2.1.0 or newer.')
sys.exit(1)
except (StandardError, Exception):
print('The Python module Cheetah is required')
sys.exit(1)
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib')))
from lib.six import moves
# We only need this for compiling an EXE and I will just always do that on 2.6+
if sys.hexversion >= 0x020600F0:
from multiprocessing import freeze_support # @UnresolvedImport
import sickbeard
from sickbeard import db, logger, network_timezones, failed_history, name_cache
from sickbeard.tv import TVShow
from sickbeard.webserveInit import WebServer
from sickbeard.databases.mainDB import MIN_DB_VERSION, MAX_DB_VERSION
from sickbeard.event_queue import Events
from sickbeard.exceptions import ex
from lib.configobj import ConfigObj
throwaway = datetime.datetime.strptime('20110101', '%Y%m%d')
rollback_loaded = None
signal.signal(signal.SIGINT, sickbeard.sig_handler)
signal.signal(signal.SIGTERM, sickbeard.sig_handler)
if 'win32' == sys.platform:
signal.signal(signal.SIGBREAK, sickbeard.sig_handler)
class SickGear(object):
def __init__(self):
# system event callback for shutdown/restart
sickbeard.events = Events(self.shutdown)
# daemon constants
self.run_as_daemon = False
self.create_pid = False
self.pid_file = ''
self.run_as_systemd = False
self.console_logging = False
# webserver constants
self.webserver = None
self.force_update = False
self.forced_port = None
self.no_launch = False
self.web_options = None
self.webhost = None
self.start_port = None
self.log_dir = None
@staticmethod
def help_message():
"""
print help message for commandline options
"""
help_msg = ['']
help_msg += ['Usage: %s <option> <another option>\n' % sickbeard.MY_FULLNAME]
help_msg += ['Options:\n']
help_tmpl = ' %-10s%-17s%s'
for ln in [
('-h', '--help', 'Prints this message'),
('-f', '--forceupdate', 'Force update all shows in the DB (from tvdb) on startup'),
('-q', '--quiet', 'Disables logging to console'),
('', '--nolaunch', 'Suppress launching web browser on startup')
]:
help_msg += [help_tmpl % ln]
if 'win32' == sys.platform:
for ln in [
('-d', '--daemon', 'Running as daemon is not supported on Windows'),
('', '', 'On Windows, --daemon is substituted with: --quiet --nolaunch')
]:
help_msg += [help_tmpl % ln]
else:
for ln in [
('-d', '--daemon', 'Run as double forked daemon (includes options --quiet --nolaunch)'),
('-s', '--systemd', 'Run as systemd service (includes options --quiet --nolaunch)'),
('', '--pidfile=<path>', 'Combined with --daemon creates a pidfile (full path including filename)')
]:
help_msg += [help_tmpl % ln]
for ln in [
('-p <port>', '--port=<port>', 'Override default/configured port to listen on'),
('', '--datadir=<path>', 'Override folder (full path) as location for'),
('', '', 'storing database, configfile, cache, logfiles'),
('', '', 'Default: %s' % sickbeard.PROG_DIR),
('', '--config=<path>', 'Override config filename (full path including filename)'),
('', '', 'to load configuration from'),
('', '', 'Default: config.ini in %s or --datadir location' % sickbeard.PROG_DIR),
('', '--noresize', 'Prevent resizing of the banner/posters even if PIL is installed')
]:
help_msg += [help_tmpl % ln]
return '\n'.join(help_msg)
@staticmethod
def execute_rollback(mo, max_v):
global rollback_loaded
try:
if None is rollback_loaded:
rollback_loaded = db.get_rollback_module()
if None is not rollback_loaded:
rollback_loaded.__dict__[mo]().run(max_v)
else:
print(u'ERROR: Could not download Rollback Module.')
except (StandardError, Exception):
pass
def start(self):
# do some preliminary stuff
sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
sickbeard.DATA_DIR = sickbeard.PROG_DIR
sickbeard.MY_ARGS = sys.argv[1:]
sickbeard.SYS_ENCODING = None
try:
locale.setlocale(locale.LC_ALL, '')
except (locale.Error, IOError):
pass
try:
sickbeard.SYS_ENCODING = locale.getpreferredencoding()
except (locale.Error, IOError):
pass
# For OSes that are poorly configured I'll just randomly force UTF-8
if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
sickbeard.SYS_ENCODING = 'UTF-8'
if not hasattr(sys, 'setdefaultencoding'):
moves.reload_module(sys)
try:
# pylint: disable=E1101
# On non-unicode builds this raises an AttributeError, if encoding type is not valid it throws a LookupError
sys.setdefaultencoding(sickbeard.SYS_ENCODING)
except (StandardError, Exception):
print('Sorry, you MUST add the SickGear folder to the PYTHONPATH environment variable')
print('or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING)
sys.exit(1)
# Need console logging for SickBeard.py and SickBeard-console.exe
self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)
# Rename the main thread
threading.currentThread().name = 'MAIN'
try:
opts, args = getopt.getopt(sys.argv[1:], 'hfqdsp::',
['help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'systemd', 'pidfile=',
'port=', 'datadir=', 'config=', 'noresize']) # @UnusedVariable
except getopt.GetoptError:
sys.exit(self.help_message())
for o, a in opts:
# Prints help message
if o in ('-h', '--help'):
sys.exit(self.help_message())
# For now we'll just silence the logging
if o in ('-q', '--quiet'):
self.console_logging = False
# Should we update (from indexer) all shows in the DB right away?
if o in ('-f', '--forceupdate'):
self.force_update = True
# Suppress launching web browser
# Needed for OSes without default browser assigned
# Prevent duplicate browser window when restarting in the app
if o in ('--nolaunch',):
self.no_launch = True
# Override default/configured port
if o in ('-p', '--port'):
try:
self.forced_port = int(a)
except ValueError:
sys.exit('Port: %s is not a number. Exiting.' % a)
# Run as a double forked daemon
if o in ('-d', '--daemon'):
self.run_as_daemon = True
# When running as daemon disable console_logging and don't start browser
self.console_logging = False
self.no_launch = True
if 'win32' == sys.platform:
self.run_as_daemon = False
# Run as a systemd service
if o in ('-s', '--systemd') and 'win32' != sys.platform:
self.run_as_systemd = True
self.run_as_daemon = False
self.console_logging = False
self.no_launch = True
# Write a pidfile if requested
if o in ('--pidfile',):
self.create_pid = True
self.pid_file = str(a)
# If the pidfile already exists, sickbeard may still be running, so exit
if os.path.exists(self.pid_file):
sys.exit('PID file: %s already exists. Exiting.' % self.pid_file)
# Specify folder to load the config file from
if o in ('--config',):
sickbeard.CONFIG_FILE = os.path.abspath(a)
# Specify folder to use as the data dir
if o in ('--datadir',):
sickbeard.DATA_DIR = os.path.abspath(a)
# Prevent resizing of the banner/posters even if PIL is installed
if o in ('--noresize',):
sickbeard.NO_RESIZE = True
# The pidfile is only useful in daemon mode, make sure we can write the file properly
if self.create_pid:
if self.run_as_daemon:
pid_dir = os.path.dirname(self.pid_file)
if not os.access(pid_dir, os.F_OK):
sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir)
if not os.access(pid_dir, os.W_OK):
sys.exit(u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir)
else:
if self.console_logging:
print(u'Not running in daemon mode. PID file creation disabled')
self.create_pid = False
# If they don't specify a config file then put it in the data dir
if not sickbeard.CONFIG_FILE:
sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini')
# Make sure that we can create the data dir
if not os.access(sickbeard.DATA_DIR, os.F_OK):
try:
os.makedirs(sickbeard.DATA_DIR, 0o744)
except os.error:
sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR)
# Make sure we can write to the data dir
if not os.access(sickbeard.DATA_DIR, os.W_OK):
sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR)
# Make sure we can write to the config file
if not os.access(sickbeard.CONFIG_FILE, os.W_OK):
if os.path.isfile(sickbeard.CONFIG_FILE):
sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE)
elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK):
sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting'
% os.path.dirname(sickbeard.CONFIG_FILE))
os.chdir(sickbeard.DATA_DIR)
if self.console_logging:
print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE)
# Load the config and publish it to the sickbeard package
if not os.path.isfile(sickbeard.CONFIG_FILE):
print(u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE)
sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
try:
stack_size = int(sickbeard.CFG['General']['stack_size'])
except (StandardError, Exception):
stack_size = None
if stack_size:
try:
threading.stack_size(stack_size)
except (StandardError, Exception) as er:
print('Stack Size %s not set: %s' % (stack_size, er.message))
# check all db versions
for d, min_v, max_v, base_v, mo in [
('failed.db', sickbeard.failed_db.MIN_DB_VERSION, sickbeard.failed_db.MAX_DB_VERSION,
sickbeard.failed_db.TEST_BASE_VERSION, 'FailedDb'),
('cache.db', sickbeard.cache_db.MIN_DB_VERSION, sickbeard.cache_db.MAX_DB_VERSION,
sickbeard.cache_db.TEST_BASE_VERSION, 'CacheDb'),
('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION, sickbeard.mainDB.MAX_DB_VERSION,
sickbeard.mainDB.TEST_BASE_VERSION, 'MainDb')
]:
cur_db_version = db.DBConnection(d).checkDBVersion()
# handling of standalone TEST db versions
if cur_db_version >= 100000 and cur_db_version != max_v:
print('Your [%s] database version (%s) is a test db version and doesn\'t match SickGear required '
'version (%s), downgrading to production db' % (d, cur_db_version, max_v))
self.execute_rollback(mo, max_v)
cur_db_version = db.DBConnection(d).checkDBVersion()
if cur_db_version >= 100000:
print(u'Rollback to production failed.')
sys.exit(u'If you have used other forks, your database may be unusable due to their changes')
if 100000 <= max_v and None is not base_v:
max_v = base_v # set max_v to the needed base production db for test_db
print(u'Rollback to production of [%s] successful.' % d)
# handling of production db versions
if 0 < cur_db_version < 100000:
if cur_db_version < min_v:
print(u'Your [%s] database version (%s) is too old to migrate from with this version of SickGear'
% (d, cur_db_version))
sys.exit(u'Upgrade using a previous version of SG first,'
+ u' or start with no database file to begin fresh')
if cur_db_version > max_v:
print(u'Your [%s] database version (%s) has been incremented past'
u' what this version of SickGear supports. Trying to rollback now. Please wait...' %
(d, cur_db_version))
self.execute_rollback(mo, max_v)
if db.DBConnection(d).checkDBVersion() > max_v:
print(u'Rollback failed.')
sys.exit(u'If you have used other forks, your database may be unusable due to their changes')
print(u'Rollback of [%s] successful.' % d)
# free memory
global rollback_loaded
rollback_loaded = None
# Initialize the config and our threads
sickbeard.initialize(console_logging=self.console_logging)
if self.run_as_daemon:
self.daemonize()
# Get PID
sickbeard.PID = os.getpid()
if self.forced_port:
logger.log(u'Forcing web server to port %s' % self.forced_port)
self.start_port = self.forced_port
else:
self.start_port = sickbeard.WEB_PORT
if sickbeard.WEB_LOG:
self.log_dir = sickbeard.LOG_DIR
else:
self.log_dir = None
# sickbeard.WEB_HOST is available as a configuration value in various
# places but is not configurable. It is supported here for historic reasons.
if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0':
self.webhost = sickbeard.WEB_HOST
else:
self.webhost = (('0.0.0.0', '::')[sickbeard.WEB_IPV6], '')[sickbeard.WEB_IPV64]
# web server options
self.web_options = dict(
host=self.webhost,
port=int(self.start_port),
web_root=sickbeard.WEB_ROOT,
data_root=os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME),
log_dir=self.log_dir,
username=sickbeard.WEB_USERNAME,
password=sickbeard.WEB_PASSWORD,
handle_reverse_proxy=sickbeard.HANDLE_REVERSE_PROXY,
enable_https=False,
https_cert=None,
https_key=None,
)
if sickbeard.ENABLE_HTTPS:
self.web_options.update(dict(
enable_https=sickbeard.ENABLE_HTTPS,
https_cert=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
https_key=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY)
))
# start web server
try:
# used to check if existing SG instances have been started
sickbeard.helpers.wait_for_free_port(
sickbeard.WEB_IPV6 and '::1' or self.web_options['host'], self.web_options['port'])
self.webserver = WebServer(self.web_options)
self.webserver.start()
except (StandardError, Exception):
logger.log(u'Unable to start web server, is something else running on port %d?' % self.start_port,
logger.ERROR)
if self.run_as_systemd:
self.exit(0)
if sickbeard.LAUNCH_BROWSER and not self.no_launch:
logger.log(u'Launching browser and exiting', logger.ERROR)
sickbeard.launch_browser(self.start_port)
self.exit(1)
# Check if we need to perform a restore first
restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
if os.path.exists(restore_dir):
if self.restore(restore_dir, sickbeard.DATA_DIR):
logger.log(u'Restore successful...')
else:
logger.log_error_and_exit(u'Restore FAILED!')
# Build from the DB to start with
self.load_shows_from_db()
# Fire up all our threads
sickbeard.start()
# Build internal name cache
name_cache.buildNameCache()
# refresh network timezones
network_timezones.update_network_dict()
# load all ids from xem
startup_background_tasks = threading.Thread(name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids)
startup_background_tasks.start()
# check history snatched_proper update
if not db.DBConnection().has_flag('history_snatch_proper'):
# noinspection PyUnresolvedReferences
history_snatched_proper_task = threading.Thread(name='UPGRADE-HISTORY-ACTION',
target=sickbeard.history.history_snatched_proper_fix)
history_snatched_proper_task.start()
if sickbeard.USE_FAILED_DOWNLOADS:
failed_history.remove_old_history()
# Start an update if we're supposed to
if self.force_update or sickbeard.UPDATE_SHOWS_ON_START:
sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable
# Launch browser
if sickbeard.LAUNCH_BROWSER and not self.no_launch:
sickbeard.launch_browser(self.start_port)
# main loop
while True:
time.sleep(1)
def daemonize(self):
"""
Fork off as a daemon
"""
# pylint: disable=E1101
# Make a non-session-leader child process
try:
pid = os.fork() # @UndefinedVariable - only available in UNIX
if pid != 0:
self.exit(0)
except OSError as er:
sys.stderr.write('fork #1 failed: %d (%s)\n' % (er.errno, er.strerror))
sys.exit(1)
os.setsid() # @UndefinedVariable - only available in UNIX
# Make sure I can read my own files and shut out others
prev = os.umask(0)
os.umask(prev and int('077', 8))
# Make the child a session-leader by detaching from the terminal
try:
pid = os.fork() # @UndefinedVariable - only available in UNIX
if pid != 0:
self.exit(0)
except OSError as er:
sys.stderr.write('fork #2 failed: %d (%s)\n' % (er.errno, er.strerror))
sys.exit(1)
# Write pid
if self.create_pid:
pid = str(os.getpid())
logger.log(u'Writing PID: %s to %s' % (pid, self.pid_file))
try:
open(self.pid_file, 'w').write('%s\n' % pid)
except IOError as er:
logger.log_error_and_exit('Unable to write PID file: %s Error: %s [%s]' % (
self.pid_file, er.strerror, er.errno))
# Redirect all output
sys.stdout.flush()
sys.stderr.flush()
devnull = getattr(os, 'devnull', '/dev/null')
stdin = open(devnull, 'r')
stdout = open(devnull, 'a+')
stderr = open(devnull, 'a+')
os.dup2(stdin.fileno(), sys.stdin.fileno())
os.dup2(stdout.fileno(), sys.stdout.fileno())
os.dup2(stderr.fileno(), sys.stderr.fileno())
@staticmethod
def remove_pid_file(pidfile):
try:
if os.path.exists(pidfile):
os.remove(pidfile)
except (IOError, OSError):
return False
return True
@staticmethod
def load_shows_from_db():
"""
Populates the showList with shows from the database
"""
logger.log(u'Loading initial show list')
my_db = db.DBConnection()
sql_results = my_db.select('SELECT * FROM tv_shows')
sickbeard.showList = []
for sqlShow in sql_results:
try:
cur_show = TVShow(int(sqlShow['indexer']), int(sqlShow['indexer_id']))
cur_show.nextEpisode()
sickbeard.showList.append(cur_show)
except Exception as er:
logger.log('There was an error creating the show in %s: %s' % (
sqlShow['location'], str(er).decode('utf-8', 'replace')), logger.ERROR)
@staticmethod
def restore(src_dir, dst_dir):
try:
for filename in os.listdir(src_dir):
src_file = os.path.join(src_dir, filename)
dst_file = os.path.join(dst_dir, filename)
bak_file = os.path.join(dst_dir, '%s.bak' % filename)
shutil.move(dst_file, bak_file)
shutil.move(src_file, dst_file)
os.rmdir(src_dir)
return True
except (StandardError, Exception):
return False
def shutdown(self, ev_type):
if sickbeard.started:
# stop all tasks
sickbeard.halt()
# save all shows to DB
sickbeard.save_all()
# shutdown web server
if self.webserver:
logger.log('Shutting down Tornado')
self.webserver.shut_down()
try:
self.webserver.join(10)
except (StandardError, Exception):
pass
# if run as daemon delete the pidfile
if self.run_as_daemon and self.create_pid:
self.remove_pid_file(self.pid_file)
if sickbeard.events.SystemEvent.RESTART == ev_type:
install_type = sickbeard.versionCheckScheduler.action.install_type
popen_list = []
if install_type in ('git', 'source'):
popen_list = [sys.executable, sickbeard.MY_FULLNAME]
if popen_list:
popen_list += sickbeard.MY_ARGS
if self.run_as_systemd:
logger.log(u'Restarting SickGear with exit(1) handler and %s' % popen_list)
logger.close()
self.exit(1)
if '--nolaunch' not in popen_list:
popen_list += ['--nolaunch']
logger.log(u'Restarting SickGear with %s' % popen_list)
logger.close()
subprocess.Popen(popen_list, cwd=os.getcwd())
# system exit
self.exit(0)
@staticmethod
def exit(code):
os._exit(code)
if __name__ == '__main__':
if sys.hexversion >= 0x020600F0:
freeze_support()
try:
try:
# start SickGear
SickGear().start()
except IOError as e:
if e.errno != errno.EINTR:
raise
except Exception as e:
logger.log('SickGear.Start() exception caught %s' % ex(e))
print(
'------------------------------------------------------------------------------------------------------------------'
)
print(
'2019 Jan: You can now run sickgear.py directly'
' (SickBeard.py now serves as a legacy convenience which prints this message on every startup)'
)
print(
'------------------------------------------------------------------------------------------------------------------'
)
import runpy
runpy.run_module('sickgear', {'_legacy_sickbeard_runner': True}, '__main__')

61
gui/slick/interfaces/default/loading.tmpl

@ -0,0 +1,61 @@
##
#from sickbeard import WEB_PORT, WEB_ROOT, PID, ENABLE_HTTPS, THEME_NAME
#set sg_root = $getVar('sbRoot', WEB_ROOT)
#set sg_pid = $getVar('sbPID', str(PID))
#set theme_suffix = ('', '-dark')['dark' == $getVar('sbThemeName', THEME_NAME)]
##
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow, noarchive, nocache, noodp, noydir, noimageindex, nosnippet">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>SickGear - Loading...</title>
<link rel="shortcut icon" href="$sg_root/images/ico/favicon.ico">
<link rel="apple-touch-icon" sizes="180x180" href="$sg_root/images/ico/apple-touch-icon-180x180.png">
<link rel="apple-touch-icon" sizes="152x152" href="$sg_root/images/ico/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="144x144" href="$sg_root/images/ico/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="120x120" href="$sg_root/images/ico/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="114x114" href="$sg_root/images/ico/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="76x76" href="$sg_root/images/ico/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="72x72" href="$sg_root/images/ico/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="60x60" href="$sg_root/images/ico/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="57x57" href="$sg_root/images/ico/apple-touch-icon-57x57.png">
<link rel="icon" type="image/png" href="$sg_root/images/ico/favicon-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="$sg_root/images/ico/favicon-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="$sg_root/images/ico/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="$sg_root/images/ico/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="$sg_root/images/ico/favicon-16x16.png" sizes="16x16">
<meta name="msapplication-TileColor" content="#2b5797">
<meta name="msapplication-TileImage" content="$sg_root/images/ico/mstile-144x144.png">
<meta name="msapplication-config" content="$sg_root/css/browserconfig.xml">
<script src="$sg_root/js/lib/jquery-2.2.4.min.js?v=$sg_pid"></script>
<script charset="utf-8">
<!--
\$.SickGear = {Root: '$sg_root'};
//-->
</script>
<script type="text/javascript" src="$sg_root/js/loadingStartup.js?v=$sg_pid"></script>
<style>
body{padding-top:0 !important}.sglogo{display:block;width:138px;height:74px;margin-bottom:-10px;background:url(${sg_root}/images/sickgear.png) no-repeat 0 0}.bfr{position:absolute;left:-999px;top:-999px}.bfr img{width:16px;height:16px}.spinner{display:inline-block;width:16px;height:16px;background:url(${sg_root}/images/loading16${theme_suffix}.gif) no-repeat 0 0}.sub-title{padding-bottom:10px}.desc, .images i{margin-right:6px}.images i{vertical-align:middle}.hide,.hide-yes,.hide-no{display:none}
</style>
<link rel="stylesheet" type="text/css" href="$sg_root/css/style.css?v=$sg_pid">
<link rel="stylesheet" type="text/css" href="$sg_root/css/#echo ('dark', 'light')['' == $theme_suffix]#.css?v=$sg_pid">
</head><body><span class="sglogo"></span>
<div class="bfr"><img src="$sg_root/images/loading16${theme_suffix}.gif"><img src="$sg_root/images/yes16.png"><img src="$sg_root/images/no16.png"></div>
<h2 class="sub-title">Starting up</h2>
#set msg_cnt = len($message) - 1
#for i, msg in enumerate($message)
<div class="loading-step">
<span class="desc grey-text" data-message="$msg">$msg: </span>
<span class="images">#if i == msg_cnt#<i class="spinner"></i>#end if#<span #if i == msg_cnt#class="hide-yes"#end if#><i class="yes"></i></span></span>
</div>
#end for
</body></html>

70
gui/slick/js/loadingStartup.js

@ -0,0 +1,70 @@
/** @namespace $.SickGear.Root */
var dev = !1,
logInfo = dev && console.info.bind(window.console) || function(){},
logErr = dev && console.error.bind(window.console) || function(){};
$(function () {
ajaxConsumer.checkLoadNotifications();
});
var baseUrl = function () {
return $.SickGear.Root;
};
var ajaxConsumer = function () {
var that = this;
that.timeoutId = 0;
that.pollInterval = 100;
logInfo('init ajaxConsumer');
return {
checkLoadNotifications : function () {
logInfo('ajaxConsumer.checkLoadNotifications()');
$.getJSON({
url: baseUrl() + '/get_message',
timeout: 15000 // timeout request after 15 secs
})
.done(function (data) {
uiUpdateComplete(data.message);
})
.fail(function (jqXHR, textStatus, errorThrown) {
if (404 === jqXHR.status) {
putMsg('Finished loading. Reloading page');
location.reload();
}
that.pollInterval = 500;
})
.always(function (jqXHR, textStatus) {
clearTimeout(that.timeoutId);
if (that.pollInterval)
that.timeoutId = setTimeout(ajaxConsumer.checkLoadNotifications, that.pollInterval);
logInfo(that.pollInterval ? '^-- ' + that.pollInterval/1000 + 's to next work' : '^-- no more work');
logInfo('====');
});
}
};
}();
function putMsg(msg) {
var loading = '.loading-step', lastStep = $(loading).filter(':last');
if (msg !== lastStep.find('.desc').attr('data-message')){
lastStep.after(lastStep.clone());
lastStep.find('.spinner').hide();
lastStep.find('.hide-yes').removeClass('hide-yes');
$(loading).filter(':last')
.find('.desc')
.attr('data-message', msg)
.text(msg + ': ');
}
}
function uiUpdateComplete(data) {
$.each(data, function (i, msg) {
if (i >= $('.loading-step').length){
putMsg(msg)
}
});
}

6
init-scripts/init.debian

@ -32,7 +32,7 @@ fi
##
## SG_USER= #$RUN_AS, username to run sickgear under, the default is sickgear
## SG_GROUP= #$RUN_GROUP, group to run sickgear under, the default is sickgear
## SG_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickgear
## SG_HOME= #$APP_PATH, the location of sickgear.py, the default is /opt/sickgear
## SG_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickgear
## SG_PIDFILE= #$PID_FILE, the location of sickgear.pid, the default is /var/run/sickgear/sickgear.pid
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
@ -56,7 +56,7 @@ RUN_AS=${SG_USER-sickgear}
# Run as group
RUN_GROUP=${SG_GROUP-sickgear}
# Path to app SG_HOME=path_to_app_SickBeard.py
# Path to app SG_HOME=path_to_app_sickgear.py
APP_PATH=${SG_HOME-/opt/sickgear}
# Data directory where sickbeard.db, cache and logs are stored
@ -76,7 +76,7 @@ EXTRA_SSD_OPTS=${SSD_OPTS-}
##
PID_PATH=$(dirname $PID_FILE)
DAEMON_OPTS=" SickBeard.py -q --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${EXTRA_DAEMON_OPTS}"
DAEMON_OPTS=" sickgear.py -q --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${EXTRA_DAEMON_OPTS}"
##

2
init-scripts/init.fedora

@ -47,7 +47,7 @@ fi
start() {
# Start daemon.
echo -n $"Starting $prog: "
daemon --user=${username} --pidfile=${pidfile} ${nice} python ${homedir}/SickBeard.py ${options}
daemon --user=${username} --pidfile=${pidfile} ${nice} python ${homedir}/sickgear.py ${options}
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch $lockfile

2
init-scripts/init.freebsd

@ -32,7 +32,7 @@ load_rc_config ${name}
: ${sickgear_datadir:="${sickgear_dir}"}
: ${sickgear_pidfile:="${sickgear_dir}/sickgear.pid"}
command="${sickgear_dir}/SickBeard.py"
command="${sickgear_dir}/sickgear.py"
command_interpreter="/usr/local/bin/python"
command_args="--daemon --pidfile ${sickgear_pidfile}"

4
init-scripts/init.gentoo

@ -11,7 +11,7 @@
#
# SICKGEAR_USER=<user you want sickgear to run under>
# SICKGEAR_GROUP=<group you want sickgear to run under>
# SICKGEAR_DIR=<path to Sickbeard.py>
# SICKGEAR_DIR=<path to sickgear.py>
# PATH_TO_PYTHON_2=/usr/bin/python2
# SICKGEAR_DATADIR=<directory that contains sickbeard.db file>
# SICKGEAR_CONFDIR=<directory that contains SickGear's config.ini file>
@ -51,7 +51,7 @@ start() {
--pidfile $(get_pidfile) \
--exec ${PATH_TO_PYTHON_2} \
-- \
${SICKGEAR_DIR}/SickBeard.py \
${SICKGEAR_DIR}/sickgear.py \
-d \
--pidfile $(get_pidfile) \
--config ${SICKGEAR_CONFDIR}/config.ini \

2
init-scripts/init.solaris11

@ -68,7 +68,7 @@
<exec_method
type='method'
name='start'
exec='/opt/sickgear/SickBeard.py --daemon'
exec='/opt/sickgear/sickgear.py --daemon'
timeout_seconds='60'>
</exec_method>

8
init-scripts/init.systemd

@ -12,7 +12,7 @@
#
# - Adjust ExecStart= to point to your python and SickGear executables.
# The FIRST token of the command line must be an ABSOLUTE FILE NAME, followed by arguments for the process.
# If no --datadir is given, data is stored in same dir as SickBeard.py
# If no --datadir is given, data is stored in same dir as sickgear.py
# Arguments can also be set in EnvironmentFile (except python)
#
# - WantedBy= specifies which target (i.e. runlevel) to start SickGear for.
@ -22,12 +22,12 @@
### Example Using simple
# Type=simple
# ExecStart=/usr/bin/python2 /opt/sickgear/app/SickBeard.py -q --nolaunch
# ExecStart=/usr/bin/python2 /opt/sickgear/app/sickgear.py -q --nolaunch
### Example Using simple with EnvironmentFile where SB_DATA=/home/sickgear/.sickgear in /etc/sickgear.conf
# Type=simple
# EnvironmentFile=/etc/sickgear.conf
# ExecStart=/usr/bin/python2 /opt/sickgear/app/SickBeard.py -q --nolaunch --datadir=${SB_DATA}
# ExecStart=/usr/bin/python2 /opt/sickgear/app/sickgear.py -q --nolaunch --datadir=${SB_DATA}
### Configuration
@ -39,7 +39,7 @@ User=sickgear
Group=sickgear
Environment=PYTHONUNBUFFERED=true
ExecStart=/usr/bin/python2 /opt/sickgear/app/SickBeard.py --systemd --datadir=/opt/sickgear/data
ExecStart=/usr/bin/python2 /opt/sickgear/app/sickgear.py --systemd --datadir=/opt/sickgear/data
KillMode=process
Restart=on-failure

6
init-scripts/init.ubuntu

@ -32,7 +32,7 @@ DESC=SickGear
## Edit user configuation in /etc/default/sickgear to change
##
## SG_USER= #$RUN_AS, username to run sickgear under, the default is sickgear
## SG_HOME= #$APP_PATH, the location of SickBeard.py, the default is /opt/sickgear
## SG_HOME= #$APP_PATH, the location of sickgear.py, the default is /opt/sickgear
## SG_DATA= #$DATA_DIR, the location of sickbeard.db, cache, logs, the default is /opt/sickgear
## SG_PIDFILE= #$PID_FILE, the location of sickgear.pid, the default is /var/run/sickgear/sickgear.pid
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
@ -47,7 +47,7 @@ DESC=SickGear
# Run as username
RUN_AS=${SG_USER-sickgear}
# Path to app SG_HOME=path_to_app_SickBeard.py
# Path to app SG_HOME=path_to_app_sickgear.py
APP_PATH=${SG_HOME-/opt/sickgear}
# Data directory where sickbeard.db, cache and logs are stored
@ -67,7 +67,7 @@ EXTRA_SSD_OPTS=${SSD_OPTS-}
##
PID_PATH=`dirname $PID_FILE`
DAEMON_OPTS=" SickBeard.py -q --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${EXTRA_DAEMON_OPTS}"
DAEMON_OPTS=" sickgear.py -q --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${EXTRA_DAEMON_OPTS}"
##

1894
sickbeard/__init__.py

File diff suppressed because it is too large

35
sickbeard/classes.py

@ -23,6 +23,8 @@ from unidecode import unidecode
import datetime
import os
import re
import threading
import copy
import sickbeard
@ -466,3 +468,36 @@ class SimpleNamespace:
def __eq__(self, other):
return self.__dict__ == other.__dict__
class LoadingMessage(object):
def __init__(self):
self.lock = threading.Lock()
self._message = [{'msg': 'Loading', 'progress': -1}]
@property
def message(self):
with self.lock:
return copy.deepcopy(self._message)
@message.setter
def message(self, msg):
with self.lock:
if 0 != len(self._message) and msg != self._message[-1:][0]['msg']:
self._message.append({'msg': msg, 'progress': -1})
def set_msg_progress(self, msg, progress):
with self.lock:
for m in self._message:
if msg == m.get('msg'):
m['progress'] = progress
return
self._message.append({'msg': msg, 'progress': progress})
def reset(self, msg=None):
msg = msg or {'msg': 'Loading', 'progress': -1}
with self.lock:
self._message = [msg]
loading_msg = LoadingMessage()

144
sickbeard/databases/mainDB.py

@ -31,6 +31,10 @@ MAX_DB_VERSION = 20010
TEST_BASE_VERSION = None # the base production db version, only needed for TEST db versions (>=100000)
def upgrade_log(to_log, log_level=logger.MESSAGE):
logger.load_log('Upgrading main db', to_log, log_level)
class MainSanityCheck(db.DBSanityCheck):
def check(self):
self.fix_missing_table_indexes()
@ -323,7 +327,7 @@ class AddSizeAndSceneNameFields(db.SchemaUpgrade):
ep_results = self.connection.select('SELECT episode_id, location, file_size FROM tv_episodes')
logger.log(u'Adding file size to all episodes in DB, please be patient')
upgrade_log(u'Adding file size to all episodes in DB, please be patient')
for cur_ep in ep_results:
if not cur_ep['location']:
continue
@ -337,7 +341,7 @@ class AddSizeAndSceneNameFields(db.SchemaUpgrade):
# check each snatch to see if we can use it to get a release name from
history_results = self.connection.select('SELECT * FROM history WHERE provider != -1 ORDER BY date ASC')
logger.log(u'Adding release name to all episodes still in history')
upgrade_log(u'Adding release name to all episodes still in history')
for cur_result in history_results:
# find the associated download, if there isn't one then ignore it
download_results = self.connection.select(
@ -346,7 +350,7 @@ class AddSizeAndSceneNameFields(db.SchemaUpgrade):
' WHERE provider = -1 AND showid = ? AND season = ? AND episode = ? AND date > ?',
[cur_result['showid'], cur_result['season'], cur_result['episode'], cur_result['date']])
if not download_results:
logger.log(u'Found a snatch in the history for ' + cur_result[
upgrade_log(u'Found a snatch in the history for ' + cur_result[
'resource'] + ' but couldn\'t find the associated download, skipping it', logger.DEBUG)
continue
@ -396,7 +400,7 @@ class AddSizeAndSceneNameFields(db.SchemaUpgrade):
# check each snatch to see if we can use it to get a release name from
empty_results = self.connection.select('SELECT episode_id, location FROM tv_episodes WHERE release_name = ""')
logger.log(u'Adding release name to all episodes with obvious scene filenames')
upgrade_log(u'Adding release name to all episodes with obvious scene filenames')
for cur_result in empty_results:
ep_file_name = ek.ek(os.path.basename, cur_result['location'])
@ -541,7 +545,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
common.Quality.UNKNOWN], [])
# update qualities (including templates)
logger.log(u'[1/4] Updating pre-defined templates and the quality for each show...', logger.MESSAGE)
upgrade_log(u'[1/4] Updating pre-defined templates and the quality for each show...', logger.MESSAGE)
cl = []
shows = self.connection.select('SELECT * FROM tv_shows')
for cur_show in shows:
@ -556,7 +560,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
# update status that are are within the old hdwebdl
# (1<<3 which is 8) and better -- exclude unknown (1<<15 which is 32768)
logger.log(u'[2/4] Updating the status for the episodes within each show...', logger.MESSAGE)
upgrade_log(u'[2/4] Updating the status for the episodes within each show...', logger.MESSAGE)
cl = []
episodes = self.connection.select('SELECT * FROM tv_episodes WHERE status < 3276800 AND status >= 800')
for cur_episode in episodes:
@ -568,7 +572,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
# may not always coordinate together
# update previous history so it shows the correct action
logger.log(u'[3/4] Updating history to reflect the correct action...', logger.MESSAGE)
upgrade_log(u'[3/4] Updating history to reflect the correct action...', logger.MESSAGE)
cl = []
history_action = self.connection.select('SELECT * FROM history WHERE action < 3276800 AND action >= 800')
for cur_entry in history_action:
@ -577,7 +581,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
self.connection.mass_action(cl)
# update previous history so it shows the correct quality
logger.log(u'[4/4] Updating history to reflect the correct quality...', logger.MESSAGE)
upgrade_log(u'[4/4] Updating history to reflect the correct quality...', logger.MESSAGE)
cl = []
history_quality = self.connection.select('SELECT * FROM history WHERE quality < 32768 AND quality >= 8')
for cur_entry in history_quality:
@ -588,7 +592,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
self.incDBVersion()
# cleanup and reduce db if any previous data was removed
logger.log(u'Performing a vacuum on the database.', logger.DEBUG)
upgrade_log(u'Performing a vacuum on the database.', logger.DEBUG)
self.connection.action('VACUUM')
return self.checkDBVersion()
@ -600,10 +604,10 @@ class AddShowidTvdbidIndex(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Checking for duplicate shows before adding unique index.')
upgrade_log(u'Checking for duplicate shows before adding unique index.')
MainSanityCheck(self.connection).fix_duplicate_shows('tvdb_id')
logger.log(u'Adding index on tvdb_id (tv_shows) and showid (tv_episodes) to speed up searches/queries.')
upgrade_log(u'Adding index on tvdb_id (tv_shows) and showid (tv_episodes) to speed up searches/queries.')
if not self.hasTable('idx_showid'):
self.connection.action('CREATE INDEX idx_showid ON tv_episodes (showid);')
if not self.hasTable('idx_tvdb_id'):
@ -619,7 +623,7 @@ class AddLastUpdateTVDB(db.SchemaUpgrade):
def execute(self):
if not self.hasColumn('tv_shows', 'last_update_tvdb'):
logger.log(u'Adding column last_update_tvdb to tv_shows')
upgrade_log(u'Adding column last_update_tvdb to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'last_update_tvdb', default=1)
@ -632,7 +636,7 @@ class AddDBIncreaseTo15(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version to v%s' % self.checkDBVersion())
upgrade_log(u'Bumping database version to v%s' % self.checkDBVersion())
self.incDBVersion()
return self.checkDBVersion()
@ -643,7 +647,7 @@ class AddIMDbInfo(db.SchemaUpgrade):
db_backed_up = False
if not self.hasTable('imdb_info'):
logger.log(u'Creating IMDb table imdb_info')
upgrade_log(u'Creating IMDb table imdb_info')
db.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True
@ -653,7 +657,7 @@ class AddIMDbInfo(db.SchemaUpgrade):
' rating TEXT, votes INTEGER, last_update NUMERIC)')
if not self.hasColumn('tv_shows', 'imdb_id'):
logger.log(u'Adding IMDb column imdb_id to tv_shows')
upgrade_log(u'Adding IMDb column imdb_id to tv_shows')
if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion())
@ -674,7 +678,7 @@ class AddProperNamingSupport(db.SchemaUpgrade):
return self.checkDBVersion()
if not self.hasColumn('tv_episodes', 'is_proper'):
logger.log(u'Adding column is_proper to tv_episodes')
upgrade_log(u'Adding column is_proper to tv_episodes')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_episodes', 'is_proper')
@ -694,7 +698,7 @@ class AddEmailSubscriptionTable(db.SchemaUpgrade):
return self.checkDBVersion()
if not self.hasColumn('tv_shows', 'notify_list'):
logger.log(u'Adding column notify_list to tv_shows')
upgrade_log(u'Adding column notify_list to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'notify_list', 'TEXT', None)
@ -718,7 +722,7 @@ class AddProperSearch(db.SchemaUpgrade):
return self.checkDBVersion()
if not self.hasColumn('info', 'last_proper_search'):
logger.log(u'Adding column last_proper_search to info')
upgrade_log(u'Adding column last_proper_search to info')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('info', 'last_proper_search', default=1)
@ -730,7 +734,7 @@ class AddProperSearch(db.SchemaUpgrade):
class AddDvdOrderOption(db.SchemaUpgrade):
def execute(self):
if not self.hasColumn('tv_shows', 'dvdorder'):
logger.log(u'Adding column dvdorder to tv_shows')
upgrade_log(u'Adding column dvdorder to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'dvdorder', 'NUMERIC', '0')
@ -742,7 +746,7 @@ class AddDvdOrderOption(db.SchemaUpgrade):
class AddSubtitlesSupport(db.SchemaUpgrade):
def execute(self):
if not self.hasColumn('tv_shows', 'subtitles'):
logger.log(u'Adding subtitles to tv_shows and tv_episodes')
upgrade_log(u'Adding subtitles to tv_shows and tv_episodes')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'subtitles')
self.addColumn('tv_episodes', 'subtitles', 'TEXT', '')
@ -758,10 +762,10 @@ class ConvertTVShowsToIndexerScheme(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Converting TV Shows table to Indexer Scheme...')
upgrade_log(u'Converting TV Shows table to Indexer Scheme...')
if self.hasTable('tmp_tv_shows'):
logger.log(u'Removing temp tv show tables left behind from previous updates...')
upgrade_log(u'Removing temp tv show tables left behind from previous updates...')
self.connection.action('DROP TABLE tmp_tv_shows')
self.connection.action('ALTER TABLE tv_shows RENAME TO tmp_tv_shows')
@ -794,10 +798,10 @@ class ConvertTVEpisodesToIndexerScheme(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Converting TV Episodes table to Indexer Scheme...')
upgrade_log(u'Converting TV Episodes table to Indexer Scheme...')
if self.hasTable('tmp_tv_episodes'):
logger.log(u'Removing temp tv episode tables left behind from previous updates...')
upgrade_log(u'Removing temp tv episode tables left behind from previous updates...')
self.connection.action('DROP TABLE tmp_tv_episodes')
self.connection.action('ALTER TABLE tv_episodes RENAME TO tmp_tv_episodes')
@ -831,10 +835,10 @@ class ConvertIMDBInfoToIndexerScheme(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Converting IMDB Info table to Indexer Scheme...')
upgrade_log(u'Converting IMDB Info table to Indexer Scheme...')
if self.hasTable('tmp_imdb_info'):
logger.log(u'Removing temp imdb info tables left behind from previous updates...')
upgrade_log(u'Removing temp imdb info tables left behind from previous updates...')
self.connection.action('DROP TABLE tmp_imdb_info')
self.connection.action('ALTER TABLE imdb_info RENAME TO tmp_imdb_info')
@ -857,10 +861,10 @@ class ConvertInfoToIndexerScheme(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Converting Info table to Indexer Scheme...')
upgrade_log(u'Converting Info table to Indexer Scheme...')
if self.hasTable('tmp_info'):
logger.log(u'Removing temp info tables left behind from previous updates...')
upgrade_log(u'Removing temp info tables left behind from previous updates...')
self.connection.action('DROP TABLE tmp_info')
self.connection.action('ALTER TABLE info RENAME TO tmp_info')
@ -881,7 +885,7 @@ class AddArchiveFirstMatchOption(db.SchemaUpgrade):
db.backup_database('sickbeard.db', self.checkDBVersion())
if not self.hasColumn('tv_shows', 'archive_firstmatch'):
logger.log(u'Adding column archive_firstmatch to tv_shows')
upgrade_log(u'Adding column archive_firstmatch to tv_shows')
self.addColumn('tv_shows', 'archive_firstmatch', 'NUMERIC', '0')
self.incDBVersion()
@ -896,7 +900,7 @@ class AddSceneNumbering(db.SchemaUpgrade):
if self.hasTable('scene_numbering'):
self.connection.action('DROP TABLE scene_numbering')
logger.log(u'Upgrading table scene_numbering ...', logger.MESSAGE)
upgrade_log(u'Upgrading table scene_numbering ...', logger.MESSAGE)
self.connection.action(
'CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER,'
' scene_season INTEGER, scene_episode INTEGER,'
@ -912,7 +916,7 @@ class ConvertIndexerToInteger(db.SchemaUpgrade):
db.backup_database('sickbeard.db', self.checkDBVersion())
cl = []
logger.log(u'Converting Indexer to Integer ...', logger.MESSAGE)
upgrade_log(u'Converting Indexer to Integer ...', logger.MESSAGE)
cl.append(['UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?', ['1', 'tvdb']])
cl.append(['UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?', ['2', 'tvrage']])
cl.append(['UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?', ['1', 'tvdb']])
@ -936,13 +940,13 @@ class AddRequireAndIgnoreWords(db.SchemaUpgrade):
db_backed_up = False
if not self.hasColumn('tv_shows', 'rls_require_words'):
logger.log(u'Adding column rls_require_words to tv_shows')
upgrade_log(u'Adding column rls_require_words to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True
self.addColumn('tv_shows', 'rls_require_words', 'TEXT', '')
if not self.hasColumn('tv_shows', 'rls_ignore_words'):
logger.log(u'Adding column rls_ignore_words to tv_shows')
upgrade_log(u'Adding column rls_ignore_words to tv_shows')
if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'rls_ignore_words', 'TEXT', '')
@ -956,14 +960,14 @@ class AddSportsOption(db.SchemaUpgrade):
def execute(self):
db_backed_up = False
if not self.hasColumn('tv_shows', 'sports'):
logger.log(u'Adding column sports to tv_shows')
upgrade_log(u'Adding column sports to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True
self.addColumn('tv_shows', 'sports', 'NUMERIC', '0')
if self.hasColumn('tv_shows', 'air_by_date') and self.hasColumn('tv_shows', 'sports'):
# update sports column
logger.log(u'[4/4] Updating tv_shows to reflect the correct sports value...', logger.MESSAGE)
upgrade_log(u'[4/4] Updating tv_shows to reflect the correct sports value...', logger.MESSAGE)
if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion())
cl = []
@ -984,7 +988,7 @@ class AddSceneNumberingToTvEpisodes(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Adding columns scene_season and scene_episode to tvepisodes')
upgrade_log(u'Adding columns scene_season and scene_episode to tvepisodes')
self.addColumn('tv_episodes', 'scene_season', 'NUMERIC', 'NULL')
self.addColumn('tv_episodes', 'scene_episode', 'NUMERIC', 'NULL')
@ -997,7 +1001,7 @@ class AddAnimeTVShow(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Adding column anime to tv_episodes')
upgrade_log(u'Adding column anime to tv_episodes')
self.addColumn('tv_shows', 'anime', 'NUMERIC', '0')
self.incDBVersion()
@ -1009,7 +1013,7 @@ class AddAbsoluteNumbering(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Adding column absolute_number to tv_episodes')
upgrade_log(u'Adding column absolute_number to tv_episodes')
self.addColumn('tv_episodes', 'absolute_number', 'NUMERIC', '0')
self.incDBVersion()
@ -1021,7 +1025,7 @@ class AddSceneAbsoluteNumbering(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Adding columns absolute_number and scene_absolute_number to scene_numbering')
upgrade_log(u'Adding columns absolute_number and scene_absolute_number to scene_numbering')
self.addColumn('scene_numbering', 'absolute_number', 'NUMERIC', '0')
self.addColumn('scene_numbering', 'scene_absolute_number', 'NUMERIC', '0')
@ -1036,7 +1040,7 @@ class AddAnimeBlacklistWhitelist(db.SchemaUpgrade):
cl = [['CREATE TABLE blacklist (show_id INTEGER, range TEXT, keyword TEXT)'],
['CREATE TABLE whitelist (show_id INTEGER, range TEXT, keyword TEXT)']]
logger.log(u'Creating table blacklist whitelist')
upgrade_log(u'Creating table blacklist whitelist')
self.connection.mass_action(cl)
self.incDBVersion()
@ -1048,7 +1052,7 @@ class AddSceneAbsoluteNumbering2(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Adding column scene_absolute_number to tv_episodes')
upgrade_log(u'Adding column scene_absolute_number to tv_episodes')
self.addColumn('tv_episodes', 'scene_absolute_number', 'NUMERIC', '0')
self.incDBVersion()
@ -1060,7 +1064,7 @@ class AddXemRefresh(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Creating table xem_refresh')
upgrade_log(u'Creating table xem_refresh')
self.connection.action(
'CREATE TABLE xem_refresh (indexer TEXT, indexer_id INTEGER PRIMARY KEY, last_refreshed INTEGER)')
@ -1073,7 +1077,7 @@ class AddSceneToTvShows(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Adding column scene to tv_shows')
upgrade_log(u'Adding column scene to tv_shows')
self.addColumn('tv_shows', 'scene', 'NUMERIC', '0')
self.incDBVersion()
@ -1088,7 +1092,7 @@ class AddIndexerMapping(db.SchemaUpgrade):
if self.hasTable('indexer_mapping'):
self.connection.action('DROP TABLE indexer_mapping')
logger.log(u'Adding table indexer_mapping')
upgrade_log(u'Adding table indexer_mapping')
self.connection.action(
'CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER, mindexer NUMERIC,'
' PRIMARY KEY (indexer_id, indexer))')
@ -1102,11 +1106,11 @@ class AddVersionToTvEpisodes(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Adding columns release_group and version to tv_episodes')
upgrade_log(u'Adding columns release_group and version to tv_episodes')
self.addColumn('tv_episodes', 'release_group', 'TEXT', '')
self.addColumn('tv_episodes', 'version', 'NUMERIC', '-1')
logger.log(u'Adding column version to history')
upgrade_log(u'Adding column version to history')
self.addColumn('history', 'version', 'NUMERIC', '-1')
self.incDBVersion()
@ -1118,7 +1122,7 @@ class BumpDatabaseVersion(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version')
upgrade_log(u'Bumping database version')
self.setDBVersion(10000)
return self.checkDBVersion()
@ -1129,7 +1133,7 @@ class Migrate41(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version')
upgrade_log(u'Bumping database version')
self.setDBVersion(10001)
return self.checkDBVersion()
@ -1145,7 +1149,7 @@ class Migrate43(db.SchemaUpgrade):
if self.hasTable(table):
db.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True
logger.log(u'Dropping redundant table tmdb_info')
upgrade_log(u'Dropping redundant table tmdb_info')
self.connection.action('DROP TABLE [%s]' % table)
db_chg = True
@ -1153,7 +1157,7 @@ class Migrate43(db.SchemaUpgrade):
if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True
logger.log(u'Dropping redundant tmdb_info refs')
upgrade_log(u'Dropping redundant tmdb_info refs')
self.dropColumn('tv_shows', 'tmdb_id')
db_chg = True
@ -1165,7 +1169,7 @@ class Migrate43(db.SchemaUpgrade):
self.connection.action('INSERT INTO db_version (db_version) VALUES (0);')
if not db_chg:
logger.log(u'Bumping database version')
upgrade_log(u'Bumping database version')
self.setDBVersion(10001)
return self.checkDBVersion()
@ -1176,7 +1180,7 @@ class Migrate4301(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version')
upgrade_log(u'Bumping database version')
self.setDBVersion(10002)
return self.checkDBVersion()
@ -1187,7 +1191,7 @@ class Migrate4302(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version')
upgrade_log(u'Bumping database version')
self.setDBVersion(10003)
return self.checkDBVersion()
@ -1198,7 +1202,7 @@ class MigrateUpstream(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Migrate SickBeard DB v%s into v15' % str(self.checkDBVersion()).replace('58', ''))
upgrade_log(u'Migrate SickBeard DB v%s into v15' % str(self.checkDBVersion()).replace('58', ''))
self.setDBVersion(15)
return self.checkDBVersion()
@ -1209,7 +1213,7 @@ class SickGearDatabaseVersion(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version to new SickGear standards')
upgrade_log(u'Bumping database version to new SickGear standards')
self.setDBVersion(20000)
return self.checkDBVersion()
@ -1220,7 +1224,7 @@ class RemoveDefaultEpStatusFromTvShows(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Dropping redundant column default_ep_status from tv_shows')
upgrade_log(u'Dropping redundant column default_ep_status from tv_shows')
self.dropColumn('tv_shows', 'default_ep_status')
self.setDBVersion(10000)
@ -1232,7 +1236,7 @@ class RemoveMinorDBVersion(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Dropping redundant column db_minor_version from db_version')
upgrade_log(u'Dropping redundant column db_minor_version from db_version')
self.dropColumn('db_version', 'db_minor_version')
self.setDBVersion(10001)
@ -1243,7 +1247,7 @@ class RemoveMinorDBVersion(db.SchemaUpgrade):
class RemoveMetadataSub(db.SchemaUpgrade):
def execute(self):
if self.hasColumn('tv_shows', 'sub_use_sr_metadata'):
logger.log(u'Dropping redundant column metadata sub')
upgrade_log(u'Dropping redundant column metadata sub')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.dropColumn('tv_shows', 'sub_use_sr_metadata')
@ -1256,10 +1260,10 @@ class DBIncreaseTo20001(db.SchemaUpgrade):
def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version to force a backup before new database code')
upgrade_log(u'Bumping database version to force a backup before new database code')
self.connection.action('VACUUM')
logger.log(u'Performed a vacuum on the database', logger.DEBUG)
upgrade_log(u'Performed a vacuum on the database', logger.DEBUG)
self.setDBVersion(20001)
return self.checkDBVersion()
@ -1269,7 +1273,7 @@ class DBIncreaseTo20001(db.SchemaUpgrade):
class AddTvShowOverview(db.SchemaUpgrade):
def execute(self):
if not self.hasColumn('tv_shows', 'overview'):
logger.log(u'Adding column overview to tv_shows')
upgrade_log(u'Adding column overview to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'overview', 'TEXT', '')
@ -1281,7 +1285,7 @@ class AddTvShowOverview(db.SchemaUpgrade):
class AddTvShowTags(db.SchemaUpgrade):
def execute(self):
if not self.hasColumn('tv_shows', 'tag'):
logger.log(u'Adding tag to tv_shows')
upgrade_log(u'Adding tag to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'tag', 'TEXT', 'Show List')
@ -1298,7 +1302,7 @@ class ChangeMapIndexer(db.SchemaUpgrade):
if self.hasTable('indexer_mapping'):
self.connection.action('DROP TABLE indexer_mapping')
logger.log(u'Changing table indexer_mapping')
upgrade_log(u'Changing table indexer_mapping')
self.connection.action(
'CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER NOT NULL,'
' mindexer NUMERIC, date NUMERIC NOT NULL DEFAULT 0, status INTEGER NOT NULL DEFAULT 0,'
@ -1307,10 +1311,10 @@ class ChangeMapIndexer(db.SchemaUpgrade):
self.connection.action('CREATE INDEX IF NOT EXISTS idx_mapping ON indexer_mapping (indexer_id, indexer)')
if not self.hasColumn('info', 'last_run_backlog'):
logger.log('Adding last_run_backlog to info')
upgrade_log('Adding last_run_backlog to info')
self.addColumn('info', 'last_run_backlog', 'NUMERIC', 1)
logger.log(u'Moving table scene_exceptions from cache.db to sickbeard.db')
upgrade_log(u'Moving table scene_exceptions from cache.db to sickbeard.db')
if self.hasTable('scene_exceptions_refresh'):
self.connection.action('DROP TABLE scene_exceptions_refresh')
self.connection.action('CREATE TABLE scene_exceptions_refresh (list TEXT PRIMARY KEY, last_refreshed INTEGER)')
@ -1353,7 +1357,7 @@ class ChangeMapIndexer(db.SchemaUpgrade):
class AddShowNotFoundCounter(db.SchemaUpgrade):
def execute(self):
if not self.hasTable('tv_shows_not_found'):
logger.log(u'Adding table tv_shows_not_found')
upgrade_log(u'Adding table tv_shows_not_found')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.connection.action(
@ -1369,7 +1373,7 @@ class AddShowNotFoundCounter(db.SchemaUpgrade):
class AddFlagTable(db.SchemaUpgrade):
def execute(self):
if not self.hasTable('flags'):
logger.log(u'Adding table flags')
upgrade_log(u'Adding table flags')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.connection.action('CREATE TABLE flags (flag PRIMARY KEY NOT NULL)')
@ -1382,7 +1386,7 @@ class AddFlagTable(db.SchemaUpgrade):
class DBIncreaseTo20007(db.SchemaUpgrade):
def execute(self):
logger.log(u'Bumping database version')
upgrade_log(u'Bumping database version')
self.setDBVersion(20007)
return self.checkDBVersion()
@ -1407,7 +1411,7 @@ class AddWatched(db.SchemaUpgrade):
self.connection.action('VACUUM')
if not self.hasTable('tv_episodes_watched'):
logger.log(u'Adding table tv_episodes_watched')
upgrade_log(u'Adding table tv_episodes_watched')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.connection.action(
@ -1425,7 +1429,7 @@ class AddPrune(db.SchemaUpgrade):
def execute(self):
if not self.hasColumn('tv_shows', 'prune'):
logger.log('Adding prune to tv_shows')
upgrade_log('Adding prune to tv_shows')
db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'prune', 'INT', 0)

15
sickbeard/db.py

@ -325,9 +325,16 @@ class DBSanityCheck(object):
pass
def load_msg(db_name, progess_msg):
logger.load_log('Upgrading %s' % db_name, progess_msg)
def upgradeDatabase(connection, schema):
logger.log(u'Checking database structure...', logger.MESSAGE)
connection.is_upgrading = False
_processUpgrade(connection, schema)
if connection.is_upgrading:
load_msg(connection.filename, 'Finished')
def prettyName(class_name):
@ -347,11 +354,13 @@ def _processUpgrade(connection, upgradeClass):
instance = upgradeClass(connection)
logger.log(u'Checking %s database upgrade' % prettyName(upgradeClass.__name__), logger.DEBUG)
if not instance.test():
connection.is_upgrading = True
load_msg(connection.filename, getattr(upgradeClass, 'pretty_name', None) or prettyName(upgradeClass.__name__))
logger.log(u'Database upgrade required: %s' % prettyName(upgradeClass.__name__), logger.MESSAGE)
try:
instance.execute()
except sqlite3.DatabaseError as e:
# attemping to restore previous DB backup and perform upgrade
# attempting to restore previous DB backup and perform upgrade
try:
instance.execute()
except:
@ -377,7 +386,7 @@ def _processUpgrade(connection, upgradeClass):
# Base migration class. All future DB changes should be subclassed from this class
class SchemaUpgrade(object):
def __init__(self, connection):
def __init__(self, connection, **kwargs):
self.connection = connection
def hasTable(self, tableName):
@ -571,6 +580,7 @@ def MigrationCode(myDB):
else:
load_msg('main db', 'Upgrading')
while db_version < sickbeard.mainDB.MAX_DB_VERSION:
if None is schema[db_version]: # skip placeholders used when multi PRs are updating DB
db_version += 1
@ -587,6 +597,7 @@ def MigrationCode(myDB):
logger.log_error_and_exit(u'Successfully restored database version: %s' % db_version)
else:
logger.log_error_and_exit(u'Failed to restore database version: %s' % db_version)
load_msg('main db', 'Finished')
def backup_database(filename, version):

12
sickbeard/logger.py

@ -325,6 +325,18 @@ def log_error_and_exit(error_msg):
sb_log_instance.log_error_and_exit(error_msg)
def load_log(load_msg, to_log, log_level=MESSAGE):
"""
logs messages and adds log_level = MESSAGE messages as progress to loading page
:param load_msg: base loading page message
:param to_log: message to log to file and as progress for loading page
:param log_level: log level (only level MESSAGE is also output as loading page progress)
"""
if MESSAGE == log_level:
sickbeard.classes.loading_msg.set_msg_progress(load_msg, to_log)
log(to_log, log_level)
def close():
sb_log_instance.close_log()

8
sickbeard/webapi.py

@ -92,6 +92,14 @@ quality_map = {'sdtv': Quality.SDTV,
quality_map_inversed = {v: k for k, v in quality_map.iteritems()}
class ApiServerLoading(webserve.BaseHandler):
@gen.coroutine
def get(self, route, *args, **kwargs):
self.finish(json.dumps({'error_msg': 'Server is loading'}))
post = get
class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):

43
sickbeard/webserve.py

@ -104,8 +104,9 @@ class PageTemplate(Template):
self.sbThemeName = sickbeard.THEME_NAME
self.log_num_errors = len(classes.ErrorViewer.errors)
self.log_num_not_found_shows = len([x for x in sickbeard.showList if 0 < x.not_found_count])
self.log_num_not_found_shows_all = len([x for x in sickbeard.showList if 0 != x.not_found_count])
if None is not sickbeard.showList:
self.log_num_not_found_shows = len([x for x in sickbeard.showList if 0 < x.not_found_count])
self.log_num_not_found_shows_all = len([x for x in sickbeard.showList if 0 != x.not_found_count])
self.sbPID = str(sickbeard.PID)
self.menu = [
{'title': 'Home', 'key': 'home'},
@ -627,6 +628,44 @@ class IsAliveHandler(BaseHandler):
self.write(results)
class LoadingWebHandler(BaseHandler):
def __init__(self, *arg, **kwargs):
super(BaseHandler, self).__init__(*arg, **kwargs)
self.lock = threading.Lock()
def page_not_found(self):
self.set_status(404)
t = PageTemplate(web_handler=self, file='404.tmpl')
return t.respond()
def loading_page(self, *args, **kwargs):
t = PageTemplate(web_handler=self, file='loading.tmpl')
t.message = classes.loading_msg.message
return t.respond()
def get_message(self, *args, **kwargs):
return json.dumps({'message': classes.loading_msg.message})
@authenticated
@gen.coroutine
def get(self, route, *args, **kwargs):
route = route.strip('/')
if 'get_message' != route:
route = 'loading_page'
try:
method = getattr(self, route)
except:
self.finish(self.page_not_found())
else:
kwargss = {k: v if not (isinstance(v, list) and 1 == len(v)) else v[0]
for k, v in self.request.arguments.iteritems() if '_xsrf' != k}
result = method(**kwargss)
if result:
self.finish(result)
post = get
class WebHandler(BaseHandler):
def __init__(self, *arg, **kwargs):
super(BaseHandler, self).__init__(*arg, **kwargs)

85
sickbeard/webserveInit.py

@ -7,8 +7,21 @@ import webapi
from sickbeard import logger
from sickbeard.helpers import create_https_certificates, re_valid_hostname
from tornado.web import Application
from tornado.web import Application, _ApplicationRouter
from tornado.ioloop import IOLoop
from tornado.routing import AnyMatches, Rule
class MyApplication(Application):
def __init__(self, *args, **kwargs):
super(MyApplication, self).__init__(*args, **kwargs)
def reset_handlers(self):
self.wildcard_router = _ApplicationRouter(self, [])
self.default_router = _ApplicationRouter(self, [
Rule(AnyMatches(), self.wildcard_router)
])
class WebServer(threading.Thread):
@ -69,30 +82,65 @@ class WebServer(threading.Thread):
sickbeard.save_config()
# Load the app
self.app = Application([],
debug=True,
serve_traceback=True,
autoreload=False,
compress_response=True,
cookie_secret=sickbeard.COOKIE_SECRET,
xsrf_cookies=True,
login_url='%s/login/' % self.options['web_root'])
self.app = MyApplication([],
debug=True,
serve_traceback=True,
autoreload=False,
compress_response=True,
cookie_secret=sickbeard.COOKIE_SECRET,
xsrf_cookies=True,
login_url='%s/login/' % self.options['web_root'])
self.re_host_pattern = re_valid_hostname()
self._add_loading_rules()
def _add_loading_rules(self):
# webui login/logout handlers
self.app.add_handlers(self.re_host_pattern, [
(r'%s/login(/?)' % self.options['web_root'], webserve.LoginHandler),
(r'%s/logout(/?)' % self.options['web_root'], webserve.LogoutHandler),
])
re_host_pattern = re_valid_hostname()
# Static File Handlers
self.app.add_handlers(self.re_host_pattern, [
# favicon
(r'%s/(favicon\.ico)' % self.options['web_root'], webserve.BaseStaticFileHandler,
{'path': os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}),
# images
(r'%s/images/(.*)' % self.options['web_root'], webserve.BaseStaticFileHandler,
{'path': os.path.join(self.options['data_root'], 'images')}),
# css
(r'%s/css/(.*)' % self.options['web_root'], webserve.BaseStaticFileHandler,
{'path': os.path.join(self.options['data_root'], 'css')}),
# javascript
(r'%s/js/(.*)' % self.options['web_root'], webserve.BaseStaticFileHandler,
{'path': os.path.join(self.options['data_root'], 'js')}),
])
# Main Handler
self.app.add_handlers(self.re_host_pattern, [
(r'%s/api(/?.*)' % self.options['web_root'], webapi.ApiServerLoading),
(r'%s/home/is_alive(/?.*)' % self.options['web_root'], webserve.IsAliveHandler),
(r'%s(/?.*)' % self.options['web_root'], webserve.LoadingWebHandler),
])
def _add_default_rules(self):
# webui login/logout handlers
self.app.add_handlers(re_host_pattern, [
self.app.add_handlers(self.re_host_pattern, [
(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)
self.app.add_handlers(re_host_pattern, [
self.app.add_handlers(self.re_host_pattern, [
(r'%s/calendar' % self.options['web_root'], webserve.CalendarHandler),
])
# Static File Handlers
self.app.add_handlers(re_host_pattern, [
self.app.add_handlers(self.re_host_pattern, [
# favicon
(r'%s/(favicon\.ico)' % self.options['web_root'], webserve.BaseStaticFileHandler,
{'path': os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}),
@ -119,7 +167,7 @@ class WebServer(threading.Thread):
])
# Main Handler
self.app.add_handlers(re_host_pattern, [
self.app.add_handlers(self.re_host_pattern, [
(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),
@ -173,6 +221,15 @@ class WebServer(threading.Thread):
# Ignore errors like 'ValueError: I/O operation on closed kqueue fd'. These might be thrown during a reload.
pass
def switch_handlers(self, new_handler='_add_default_rules'):
if hasattr(self, new_handler):
def d_f(s, nh):
s.app.reset_handlers()
getattr(s, nh)()
sickbeard.classes.loading_msg.reset()
self.io_loop.add_callback(d_f, self, new_handler)
logger.log('Switching HTTP Server handlers to %s' % new_handler, logger.DEBUG)
def shut_down(self):
self.alive = False
if None is not self.io_loop:

139
sickgear.py

@ -1,6 +1,4 @@
#!/usr/bin/env python2
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of SickGear.
#
@ -73,7 +71,6 @@ import sickbeard
from sickbeard import db, logger, network_timezones, failed_history, name_cache
from sickbeard.tv import TVShow
from sickbeard.webserveInit import WebServer
from sickbeard.databases.mainDB import MIN_DB_VERSION, MAX_DB_VERSION
from sickbeard.event_queue import Events
from sickbeard.exceptions import ex
from lib.configobj import ConfigObj
@ -158,13 +155,15 @@ class SickGear(object):
return '\n'.join(help_msg)
@staticmethod
def execute_rollback(mo, max_v):
def execute_rollback(mo, max_v, load_msg):
global rollback_loaded
try:
if None is rollback_loaded:
rollback_loaded = db.get_rollback_module()
if None is not rollback_loaded:
rollback_loaded.__dict__[mo]().run(max_v)
rc = rollback_loaded.__dict__[mo]()
rc.load_msg = load_msg
rc.run(max_v)
else:
print(u'ERROR: Could not download Rollback Module.')
except (StandardError, Exception):
@ -204,7 +203,7 @@ class SickGear(object):
print('or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING)
sys.exit(1)
# Need console logging for SickBeard.py and SickBeard-console.exe
# Need console logging for sickgear.py and SickBeard-console.exe
self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)
# Rename the main thread
@ -339,60 +338,15 @@ class SickGear(object):
except (StandardError, Exception) as er:
print('Stack Size %s not set: %s' % (stack_size, er.message))
# check all db versions
for d, min_v, max_v, base_v, mo in [
('failed.db', sickbeard.failed_db.MIN_DB_VERSION, sickbeard.failed_db.MAX_DB_VERSION,
sickbeard.failed_db.TEST_BASE_VERSION, 'FailedDb'),
('cache.db', sickbeard.cache_db.MIN_DB_VERSION, sickbeard.cache_db.MAX_DB_VERSION,
sickbeard.cache_db.TEST_BASE_VERSION, 'CacheDb'),
('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION, sickbeard.mainDB.MAX_DB_VERSION,
sickbeard.mainDB.TEST_BASE_VERSION, 'MainDb')
]:
cur_db_version = db.DBConnection(d).checkDBVersion()
# handling of standalone TEST db versions
if cur_db_version >= 100000 and cur_db_version != max_v:
print('Your [%s] database version (%s) is a test db version and doesn\'t match SickGear required '
'version (%s), downgrading to production db' % (d, cur_db_version, max_v))
self.execute_rollback(mo, max_v)
cur_db_version = db.DBConnection(d).checkDBVersion()
if cur_db_version >= 100000:
print(u'Rollback to production failed.')
sys.exit(u'If you have used other forks, your database may be unusable due to their changes')
if 100000 <= max_v and None is not base_v:
max_v = base_v # set max_v to the needed base production db for test_db
print(u'Rollback to production of [%s] successful.' % d)
# handling of production db versions
if 0 < cur_db_version < 100000:
if cur_db_version < min_v:
print(u'Your [%s] database version (%s) is too old to migrate from with this version of SickGear'
% (d, cur_db_version))
sys.exit(u'Upgrade using a previous version of SG first,'
+ u' or start with no database file to begin fresh')
if cur_db_version > max_v:
print(u'Your [%s] database version (%s) has been incremented past'
u' what this version of SickGear supports. Trying to rollback now. Please wait...' %
(d, cur_db_version))
self.execute_rollback(mo, max_v)
if db.DBConnection(d).checkDBVersion() > max_v:
print(u'Rollback failed.')
sys.exit(u'If you have used other forks, your database may be unusable due to their changes')
print(u'Rollback of [%s] successful.' % d)
# free memory
global rollback_loaded
rollback_loaded = None
# Initialize the config and our threads
sickbeard.initialize(console_logging=self.console_logging)
if self.run_as_daemon:
self.daemonize()
# Get PID
sickbeard.PID = os.getpid()
# Initialize the config
sickbeard.initialize(console_logging=self.console_logging)
if self.forced_port:
logger.log(u'Forcing web server to port %s' % self.forced_port)
self.start_port = self.forced_port
@ -440,6 +394,7 @@ class SickGear(object):
self.webserver = WebServer(self.web_options)
self.webserver.start()
sickbeard.started = True
except (StandardError, Exception):
logger.log(u'Unable to start web server, is something else running on port %d?' % self.start_port,
logger.ERROR)
@ -450,30 +405,95 @@ class SickGear(object):
sickbeard.launch_browser(self.start_port)
self.exit(1)
# Launch browser
if sickbeard.LAUNCH_BROWSER and not self.no_launch:
sickbeard.launch_browser(self.start_port)
# check all db versions
for d, min_v, max_v, base_v, mo in [
('failed.db', sickbeard.failed_db.MIN_DB_VERSION, sickbeard.failed_db.MAX_DB_VERSION,
sickbeard.failed_db.TEST_BASE_VERSION, 'FailedDb'),
('cache.db', sickbeard.cache_db.MIN_DB_VERSION, sickbeard.cache_db.MAX_DB_VERSION,
sickbeard.cache_db.TEST_BASE_VERSION, 'CacheDb'),
('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION, sickbeard.mainDB.MAX_DB_VERSION,
sickbeard.mainDB.TEST_BASE_VERSION, 'MainDb')
]:
cur_db_version = db.DBConnection(d).checkDBVersion()
# handling of standalone TEST db versions
load_msg = 'Downgrading %s to production version' % d
if cur_db_version >= 100000 and cur_db_version != max_v:
sickbeard.classes.loading_msg.set_msg_progress(load_msg, 'Rollback')
print('Your [%s] database version (%s) is a test db version and doesn\'t match SickGear required '
'version (%s), downgrading to production db' % (d, cur_db_version, max_v))
self.execute_rollback(mo, max_v, load_msg)
cur_db_version = db.DBConnection(d).checkDBVersion()
if cur_db_version >= 100000:
print(u'Rollback to production failed.')
sys.exit(u'If you have used other forks, your database may be unusable due to their changes')
if 100000 <= max_v and None is not base_v:
max_v = base_v # set max_v to the needed base production db for test_db
print(u'Rollback to production of [%s] successful.' % d)
sickbeard.classes.loading_msg.set_msg_progress(load_msg, 'Finished')
# handling of production db versions
if 0 < cur_db_version < 100000:
if cur_db_version < min_v:
print(u'Your [%s] database version (%s) is too old to migrate from with this version of SickGear'
% (d, cur_db_version))
sys.exit(u'Upgrade using a previous version of SG first,'
+ u' or start with no database file to begin fresh')
if cur_db_version > max_v:
sickbeard.classes.loading_msg.set_msg_progress(load_msg, 'Rollback')
print(u'Your [%s] database version (%s) has been incremented past'
u' what this version of SickGear supports. Trying to rollback now. Please wait...' %
(d, cur_db_version))
self.execute_rollback(mo, max_v, load_msg)
if db.DBConnection(d).checkDBVersion() > max_v:
print(u'Rollback failed.')
sys.exit(u'If you have used other forks, your database may be unusable due to their changes')
print(u'Rollback of [%s] successful.' % d)
sickbeard.classes.loading_msg.set_msg_progress(load_msg, 'Finished')
# free memory
global rollback_loaded
rollback_loaded = None
sickbeard.classes.loading_msg.message = 'Init SickGear'
# Initialize the threads and other stuff
sickbeard.initialize(console_logging=self.console_logging)
# Check if we need to perform a restore first
restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
if os.path.exists(restore_dir):
sickbeard.classes.loading_msg.message = 'Restoring files'
if self.restore(restore_dir, sickbeard.DATA_DIR):
logger.log(u'Restore successful...')
else:
logger.log_error_and_exit(u'Restore FAILED!')
# Build from the DB to start with
sickbeard.classes.loading_msg.message = 'Loading shows from db'
self.load_shows_from_db()
# Fire up all our threads
sickbeard.classes.loading_msg.message = 'Starting threads'
sickbeard.start()
# Build internal name cache
sickbeard.classes.loading_msg.message = 'Build name cache'
name_cache.buildNameCache()
# refresh network timezones
sickbeard.classes.loading_msg.message = 'Checking network timezones'
network_timezones.update_network_dict()
# load all ids from xem
sickbeard.classes.loading_msg.message = 'Loading xem data'
startup_background_tasks = threading.Thread(name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids)
startup_background_tasks.start()
sickbeard.classes.loading_msg.message = 'Checking history'
# check history snatched_proper update
if not db.DBConnection().has_flag('history_snatch_proper'):
# noinspection PyUnresolvedReferences
@ -486,12 +506,17 @@ class SickGear(object):
# Start an update if we're supposed to
if self.force_update or sickbeard.UPDATE_SHOWS_ON_START:
sickbeard.classes.loading_msg.message = 'Starting a forced show update'
sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable
# Launch browser
if sickbeard.LAUNCH_BROWSER and not self.no_launch:
sickbeard.launch_browser(self.start_port)
sickbeard.classes.loading_msg.message = 'Switching to default web server'
time.sleep(2)
self.webserver.switch_handlers()
# # Launch browser
# if sickbeard.LAUNCH_BROWSER and not self.no_launch:
# sickbeard.launch_browser(self.start_port)
# main loop
while True:
time.sleep(1)

4
tests/compatibility_tests.py

@ -14,7 +14,7 @@ class CompatibilityTests(unittest.TestCase):
if x.endswith('.py'):
pyfiles.append(os.path.join(dirpath, x))
pyfiles.append(os.path.join(path,'SickBeard.py'))
pyfiles.append(os.path.join(path, 'sickgear.py'))
output = subprocess.Popen('2to3'
' -f except'
@ -28,4 +28,4 @@ class CompatibilityTests(unittest.TestCase):
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(QualityTests)
unittest.TextTestRunner(verbosity=2).run(suite)
unittest.TextTestRunner(verbosity=2).run(suite)

Loading…
Cancel
Save