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. 652
      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. 160
      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. 39
      sickbeard/webserve.py
  19. 71
      sickbeard/webserveInit.py
  20. 139
      sickgear.py
  21. 2
      tests/compatibility_tests.py

5
CHANGES.md

@ -27,6 +27,11 @@
* Update urllib3 release 1.24.3 (324e47a) to 1.25.2 (49eea80) * 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 win_inet_pton 1.0.1 (934a852) to 1.1.0 (57e3558)
* Update xmltodict library 0.11.0 (79ac9a4) to 0.12.0 (f3ab7e1) * 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] [develop changelog]

652
SickBeard.py

@ -1,6 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python2
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
# #
# This file is part of SickGear. # This file is part of SickGear.
# #
@ -17,644 +15,16 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>. # 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 print_function
from __future__ import with_statement print(
'------------------------------------------------------------------------------------------------------------------'
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: print(
self.web_options.update(dict( '2019 Jan: You can now run sickgear.py directly'
enable_https=sickbeard.ENABLE_HTTPS, ' (SickBeard.py now serves as a legacy convenience which prints this message on every startup)'
https_cert=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT), )
https_key=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY) print(
)) '------------------------------------------------------------------------------------------------------------------'
)
# start web server import runpy
try: runpy.run_module('sickgear', {'_legacy_sickbeard_runner': True}, '__main__')
# 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))

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_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_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_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 ## 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 ## 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 as group
RUN_GROUP=${SG_GROUP-sickgear} 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} APP_PATH=${SG_HOME-/opt/sickgear}
# Data directory where sickbeard.db, cache and logs are stored # Data directory where sickbeard.db, cache and logs are stored
@ -76,7 +76,7 @@ EXTRA_SSD_OPTS=${SSD_OPTS-}
## ##
PID_PATH=$(dirname $PID_FILE) 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() {
# Start daemon. # Start daemon.
echo -n $"Starting $prog: " 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=$? RETVAL=$?
echo echo
[ $RETVAL -eq 0 ] && touch $lockfile [ $RETVAL -eq 0 ] && touch $lockfile

2
init-scripts/init.freebsd

@ -32,7 +32,7 @@ load_rc_config ${name}
: ${sickgear_datadir:="${sickgear_dir}"} : ${sickgear_datadir:="${sickgear_dir}"}
: ${sickgear_pidfile:="${sickgear_dir}/sickgear.pid"} : ${sickgear_pidfile:="${sickgear_dir}/sickgear.pid"}
command="${sickgear_dir}/SickBeard.py" command="${sickgear_dir}/sickgear.py"
command_interpreter="/usr/local/bin/python" command_interpreter="/usr/local/bin/python"
command_args="--daemon --pidfile ${sickgear_pidfile}" 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_USER=<user you want sickgear to run under>
# SICKGEAR_GROUP=<group 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 # PATH_TO_PYTHON_2=/usr/bin/python2
# SICKGEAR_DATADIR=<directory that contains sickbeard.db file> # SICKGEAR_DATADIR=<directory that contains sickbeard.db file>
# SICKGEAR_CONFDIR=<directory that contains SickGear's config.ini file> # SICKGEAR_CONFDIR=<directory that contains SickGear's config.ini file>
@ -51,7 +51,7 @@ start() {
--pidfile $(get_pidfile) \ --pidfile $(get_pidfile) \
--exec ${PATH_TO_PYTHON_2} \ --exec ${PATH_TO_PYTHON_2} \
-- \ -- \
${SICKGEAR_DIR}/SickBeard.py \ ${SICKGEAR_DIR}/sickgear.py \
-d \ -d \
--pidfile $(get_pidfile) \ --pidfile $(get_pidfile) \
--config ${SICKGEAR_CONFDIR}/config.ini \ --config ${SICKGEAR_CONFDIR}/config.ini \

2
init-scripts/init.solaris11

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

8
init-scripts/init.systemd

@ -12,7 +12,7 @@
# #
# - Adjust ExecStart= to point to your python and SickGear executables. # - 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. # 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) # Arguments can also be set in EnvironmentFile (except python)
# #
# - WantedBy= specifies which target (i.e. runlevel) to start SickGear for. # - WantedBy= specifies which target (i.e. runlevel) to start SickGear for.
@ -22,12 +22,12 @@
### Example Using simple ### Example Using simple
# Type=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 ### Example Using simple with EnvironmentFile where SB_DATA=/home/sickgear/.sickgear in /etc/sickgear.conf
# Type=simple # Type=simple
# EnvironmentFile=/etc/sickgear.conf # 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 ### Configuration
@ -39,7 +39,7 @@ User=sickgear
Group=sickgear Group=sickgear
Environment=PYTHONUNBUFFERED=true 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 KillMode=process
Restart=on-failure Restart=on-failure

6
init-scripts/init.ubuntu

@ -32,7 +32,7 @@ DESC=SickGear
## Edit user configuation in /etc/default/sickgear to change ## Edit user configuation in /etc/default/sickgear to change
## ##
## SG_USER= #$RUN_AS, username to run sickgear under, the default is sickgear ## 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_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 ## 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 ## 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 username
RUN_AS=${SG_USER-sickgear} 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} APP_PATH=${SG_HOME-/opt/sickgear}
# Data directory where sickbeard.db, cache and logs are stored # Data directory where sickbeard.db, cache and logs are stored
@ -67,7 +67,7 @@ EXTRA_SSD_OPTS=${SSD_OPTS-}
## ##
PID_PATH=`dirname $PID_FILE` 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}"
## ##

160
sickbeard/__init__.py

@ -98,7 +98,7 @@ SHOW_UPDATE_HOUR = 3
# non ui settings # non ui settings
REMOVE_FILENAME_CHARS = None REMOVE_FILENAME_CHARS = None
IMPORT_DEFAULT_CHECKED_SHOWS = None IMPORT_DEFAULT_CHECKED_SHOWS = 0
# /non ui settings # /non ui settings
providerList = [] providerList = []
@ -127,13 +127,13 @@ FILE_LOGGING_PRESET = 'DB'
SOCKET_TIMEOUT = None SOCKET_TIMEOUT = None
WEB_PORT = None WEB_PORT = None
WEB_LOG = None WEB_LOG = 0
WEB_ROOT = None WEB_ROOT = None
WEB_USERNAME = None WEB_USERNAME = None
WEB_PASSWORD = None WEB_PASSWORD = None
WEB_HOST = None WEB_HOST = None
WEB_IPV6 = None WEB_IPV6 = 0
WEB_IPV64 = None WEB_IPV64 = 0
HANDLE_REVERSE_PROXY = False HANDLE_REVERSE_PROXY = False
SEND_SECURITY_HEADERS = True SEND_SECURITY_HEADERS = True
@ -178,14 +178,14 @@ METADATA_KODI = None
RESULTS_SORTBY = None RESULTS_SORTBY = None
QUALITY_DEFAULT = None QUALITY_DEFAULT = SD
STATUS_DEFAULT = None STATUS_DEFAULT = SKIPPED
WANTED_BEGIN_DEFAULT = None WANTED_BEGIN_DEFAULT = 0
WANTED_LATEST_DEFAULT = None WANTED_LATEST_DEFAULT = 0
FLATTEN_FOLDERS_DEFAULT = False FLATTEN_FOLDERS_DEFAULT = False
SUBTITLES_DEFAULT = False SUBTITLES_DEFAULT = False
INDEXER_DEFAULT = None INDEXER_DEFAULT = 0
INDEXER_TIMEOUT = None INDEXER_TIMEOUT = 20
SCENE_DEFAULT = False SCENE_DEFAULT = False
ANIME_DEFAULT = False ANIME_DEFAULT = False
USE_IMDB_INFO = True USE_IMDB_INFO = True
@ -206,14 +206,14 @@ NAMING_ANIME_PATTERN = None
NAMING_CUSTOM_ANIME = False NAMING_CUSTOM_ANIME = False
NAMING_FORCE_FOLDERS = False NAMING_FORCE_FOLDERS = False
NAMING_STRIP_YEAR = False NAMING_STRIP_YEAR = False
NAMING_ANIME = None NAMING_ANIME = 3
USE_NZBS = False USE_NZBS = False
USE_TORRENTS = False USE_TORRENTS = False
NZB_METHOD = None NZB_METHOD = None
NZB_DIR = None NZB_DIR = None
USENET_RETENTION = None USENET_RETENTION = 500
TORRENT_METHOD = None TORRENT_METHOD = None
TORRENT_DIR = None TORRENT_DIR = None
DOWNLOAD_PROPERS = False DOWNLOAD_PROPERS = False
@ -222,19 +222,20 @@ WEBDL_TYPES = []
ALLOW_HIGH_PRIORITY = False ALLOW_HIGH_PRIORITY = False
NEWZNAB_DATA = '' NEWZNAB_DATA = ''
AUTOPOSTPROCESSER_FREQUENCY = None
RECENTSEARCH_FREQUENCY = 0
UPDATE_FREQUENCY = None
RECENTSEARCH_STARTUP = False
BACKLOG_FREQUENCY = None
BACKLOG_NOFULL = False
DEFAULT_AUTOPOSTPROCESSER_FREQUENCY = 10 DEFAULT_AUTOPOSTPROCESSER_FREQUENCY = 10
DEFAULT_RECENTSEARCH_FREQUENCY = 40
DEFAULT_BACKLOG_FREQUENCY = 21 DEFAULT_BACKLOG_FREQUENCY = 21
DEFAULT_RECENTSEARCH_FREQUENCY = 40
DEFAULT_UPDATE_FREQUENCY = 1 DEFAULT_UPDATE_FREQUENCY = 1
DEFAULT_WATCHEDSTATE_FREQUENCY = 10 DEFAULT_WATCHEDSTATE_FREQUENCY = 10
AUTOPOSTPROCESSER_FREQUENCY = DEFAULT_AUTOPOSTPROCESSER_FREQUENCY
BACKLOG_FREQUENCY = DEFAULT_BACKLOG_FREQUENCY
RECENTSEARCH_FREQUENCY = DEFAULT_RECENTSEARCH_FREQUENCY
UPDATE_FREQUENCY = DEFAULT_UPDATE_FREQUENCY
RECENTSEARCH_STARTUP = False
BACKLOG_NOFULL = False
MIN_AUTOPOSTPROCESSER_FREQUENCY = 1 MIN_AUTOPOSTPROCESSER_FREQUENCY = 1
MIN_RECENTSEARCH_FREQUENCY = 10 MIN_RECENTSEARCH_FREQUENCY = 10
MIN_BACKLOG_FREQUENCY = 7 MIN_BACKLOG_FREQUENCY = 7
@ -293,7 +294,7 @@ EMBY_PARENT_MAPS = None
EMBY_HOST = None EMBY_HOST = None
EMBY_APIKEY = None EMBY_APIKEY = None
EMBY_WATCHEDSTATE_SCHEDULED = False EMBY_WATCHEDSTATE_SCHEDULED = False
EMBY_WATCHEDSTATE_FREQUENCY = 0 EMBY_WATCHEDSTATE_FREQUENCY = DEFAULT_WATCHEDSTATE_FREQUENCY
USE_KODI = False USE_KODI = False
KODI_ALWAYS_ON = True KODI_ALWAYS_ON = True
@ -319,7 +320,7 @@ PLEX_HOST = None
PLEX_USERNAME = None PLEX_USERNAME = None
PLEX_PASSWORD = None PLEX_PASSWORD = None
PLEX_WATCHEDSTATE_SCHEDULED = False PLEX_WATCHEDSTATE_SCHEDULED = False
PLEX_WATCHEDSTATE_FREQUENCY = 0 PLEX_WATCHEDSTATE_FREQUENCY = DEFAULT_WATCHEDSTATE_FREQUENCY
USE_XBMC = False USE_XBMC = False
XBMC_ALWAYS_ON = True XBMC_ALWAYS_ON = True
@ -494,7 +495,7 @@ EPISODE_VIEW_LAYOUT = None
EPISODE_VIEW_SORT = None EPISODE_VIEW_SORT = None
EPISODE_VIEW_DISPLAY_PAUSED = False EPISODE_VIEW_DISPLAY_PAUSED = False
EPISODE_VIEW_POSTERS = True EPISODE_VIEW_POSTERS = True
EPISODE_VIEW_MISSED_RANGE = None EPISODE_VIEW_MISSED_RANGE = 7
HISTORY_LAYOUT = None HISTORY_LAYOUT = None
BROWSELIST_HIDDEN = [] BROWSELIST_HIDDEN = []
@ -562,6 +563,7 @@ COOKIE_SECRET = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
CACHE_IMAGE_URL_LIST = classes.ImageUrlList() CACHE_IMAGE_URL_LIST = classes.ImageUrlList()
__INITIALIZED__ = False __INITIALIZED__ = False
__INIT_STAGE__ = 0
def get_backlog_cycle_time(): def get_backlog_cycle_time():
@ -573,20 +575,28 @@ def initialize(console_logging=True):
with INIT_LOCK: with INIT_LOCK:
# Misc # Misc
global __INITIALIZED__, showList, providerList, newznabProviderList, torrentRssProviderList, \ global __INITIALIZED__, __INIT_STAGE__
if __INITIALIZED__:
return False
__INIT_STAGE__ += 1
if 1 == __INIT_STAGE__:
init_stage_1(console_logging)
else:
return init_stage_2()
def init_stage_1(console_logging):
# Misc
global showList, providerList, newznabProviderList, torrentRssProviderList, \
WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, CREATE_MISSING_SHOW_DIRS, \ WEB_HOST, WEB_ROOT, ACTUAL_CACHE_DIR, CACHE_DIR, ZONEINFO_DIR, ADD_SHOWS_WO_DIR, CREATE_MISSING_SHOW_DIRS, \
SHOW_DIRS_WITH_DOTS, \ SHOW_DIRS_WITH_DOTS, \
RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, INDEXER_DEFAULT, \ RECENTSEARCH_STARTUP, NAMING_FORCE_FOLDERS, SOCKET_TIMEOUT, DEBUG, INDEXER_DEFAULT, CONFIG_FILE, \
CONFIG_FILE, CONFIG_VERSION, \ CONFIG_FILE, CONFIG_VERSION, \
REMOVE_FILENAME_CHARS, IMPORT_DEFAULT_CHECKED_SHOWS, WANTEDLIST_CACHE, MODULE_UPDATE_STRING, EXT_UPDATES REMOVE_FILENAME_CHARS, IMPORT_DEFAULT_CHECKED_SHOWS, WANTEDLIST_CACHE, MODULE_UPDATE_STRING, EXT_UPDATES
# Schedulers
# global traktCheckerScheduler
global recentSearchScheduler, backlogSearchScheduler, showUpdateScheduler, \
versionCheckScheduler, showQueueScheduler, searchQueueScheduler, \
properFinderScheduler, autoPostProcesserScheduler, subtitlesFinderScheduler, \
background_mapping_task, provider_ping_thread_pool, \
embyWatchedStateScheduler, plexWatchedStateScheduler, watchedStateQueueScheduler, \
MIN_WATCHEDSTATE_FREQUENCY, MAX_WATCHEDSTATE_FREQUENCY, DEFAULT_WATCHEDSTATE_FREQUENCY
# Add Show Search # Add Show Search
global RESULTS_SORTBY global RESULTS_SORTBY
# Add Show Defaults # Add Show Defaults
@ -642,7 +652,7 @@ def initialize(console_logging=True):
NAMING_CUSTOM_SPORTS, NAMING_SPORTS_PATTERN, \ NAMING_CUSTOM_SPORTS, NAMING_SPORTS_PATTERN, \
NAMING_CUSTOM_ANIME, NAMING_ANIME_PATTERN, NAMING_ANIME_MULTI_EP, NAMING_ANIME NAMING_CUSTOM_ANIME, NAMING_ANIME_PATTERN, NAMING_ANIME_MULTI_EP, NAMING_ANIME
# Post Processing/Metadata # Post Processing/Metadata
global metadata_provider_dict, METADATA_KODI, METADATA_MEDE8ER, METADATA_XBMC, METADATA_MEDIABROWSER, \ global METADATA_KODI, METADATA_MEDE8ER, METADATA_XBMC, METADATA_MEDIABROWSER, \
METADATA_PS3, METADATA_TIVO, METADATA_WDTV, METADATA_XBMC_12PLUS METADATA_PS3, METADATA_TIVO, METADATA_WDTV, METADATA_XBMC_12PLUS
# Notification Settings/HT and NAS # Notification Settings/HT and NAS
global USE_EMBY, EMBY_UPDATE_LIBRARY, EMBY_PARENT_MAPS, EMBY_HOST, EMBY_APIKEY, \ global USE_EMBY, EMBY_UPDATE_LIBRARY, EMBY_PARENT_MAPS, EMBY_HOST, EMBY_APIKEY, \
@ -694,9 +704,6 @@ def initialize(console_logging=True):
# Anime Settings # Anime Settings
global ANIME_TREAT_AS_HDTV, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST global ANIME_TREAT_AS_HDTV, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST
if __INITIALIZED__:
return False
for stanza in ('General', 'Blackhole', 'SABnzbd', 'NZBGet', 'Emby', 'Kodi', 'XBMC', 'PLEX', for stanza in ('General', 'Blackhole', 'SABnzbd', 'NZBGet', 'Emby', 'Kodi', 'XBMC', 'PLEX',
'Growl', 'Prowl', 'Twitter', 'Slack', 'Discordapp', 'Boxcar2', 'NMJ', 'NMJv2', 'Growl', 'Prowl', 'Twitter', 'Slack', 'Discordapp', 'Boxcar2', 'NMJ', 'NMJv2',
'Synology', 'SynologyNotifier', 'Synology', 'SynologyNotifier',
@ -1325,7 +1332,52 @@ def initialize(console_logging=True):
pass pass
logger.sb_log_instance.init_logging(console_logging=console_logging) logger.sb_log_instance.init_logging(console_logging=console_logging)
# initialize the main SB database try:
import _scandir
except ImportError:
_scandir = None
try:
import ctypes
except ImportError:
ctypes = None
if None is not _scandir and None is not ctypes and not getattr(_scandir, 'DirEntry', None):
MODULE_UPDATE_STRING = \
'Your scandir binary module is outdated, using the slow but newer Python module.' \
'<br>Upgrade the binary at a command prompt with' \
' # <span class="boldest">python -m pip install -U scandir</span>' \
'<br>Important: You <span class="boldest">must</span> Shutdown SickGear before upgrading'
showList = []
def init_stage_2():
# Misc
global __INITIALIZED__, RECENTSEARCH_STARTUP
# Schedulers
# global traktCheckerScheduler
global recentSearchScheduler, backlogSearchScheduler, showUpdateScheduler, \
versionCheckScheduler, showQueueScheduler, searchQueueScheduler, \
properFinderScheduler, autoPostProcesserScheduler, subtitlesFinderScheduler, \
background_mapping_task, \
watchedStateQueueScheduler, embyWatchedStateScheduler, plexWatchedStateScheduler
# Gen Config/Misc
global SHOW_UPDATE_HOUR, UPDATE_FREQUENCY
# Search Settings/Episode
global RECENTSEARCH_FREQUENCY
# Subtitles
global USE_SUBTITLES, SUBTITLES_FINDER_FREQUENCY
# Post Processing/Post-Processing
global PROCESS_AUTOMATICALLY, AUTOPOSTPROCESSER_FREQUENCY
# Post Processing/Metadata
global metadata_provider_dict, METADATA_KODI, METADATA_MEDE8ER, METADATA_MEDIABROWSER, \
METADATA_PS3, METADATA_TIVO, METADATA_WDTV, METADATA_XBMC, METADATA_XBMC_12PLUS
# Notification Settings/HT and NAS
global EMBY_WATCHEDSTATE_FREQUENCY, PLEX_WATCHEDSTATE_FREQUENCY
# initialize main database
my_db = db.DBConnection() my_db = db.DBConnection()
db.MigrationCode(my_db) db.MigrationCode(my_db)
@ -1347,14 +1399,14 @@ def initialize(console_logging=True):
# initialize metadata_providers # initialize metadata_providers
metadata_provider_dict = metadata.get_metadata_generator_dict() metadata_provider_dict = metadata.get_metadata_generator_dict()
for cur_metadata_tuple in [(METADATA_XBMC, metadata.xbmc), for cur_metadata_tuple in [(METADATA_KODI, metadata.kodi),
(METADATA_XBMC_12PLUS, metadata.xbmc_12plus), (METADATA_MEDE8ER, metadata.mede8er),
(METADATA_MEDIABROWSER, metadata.mediabrowser), (METADATA_MEDIABROWSER, metadata.mediabrowser),
(METADATA_PS3, metadata.ps3), (METADATA_PS3, metadata.ps3),
(METADATA_WDTV, metadata.wdtv),
(METADATA_TIVO, metadata.tivo), (METADATA_TIVO, metadata.tivo),
(METADATA_MEDE8ER, metadata.mede8er), (METADATA_WDTV, metadata.wdtv),
(METADATA_KODI, metadata.kodi), (METADATA_XBMC, metadata.xbmc),
(METADATA_XBMC_12PLUS, metadata.xbmc_12plus),
]: ]:
(cur_metadata_config, cur_metadata_class) = cur_metadata_tuple (cur_metadata_config, cur_metadata_class) = cur_metadata_tuple
tmp_provider = cur_metadata_class.metadata_class() tmp_provider = cur_metadata_class.metadata_class()
@ -1451,8 +1503,6 @@ def initialize(console_logging=True):
threadName='FINDSUBTITLES', threadName='FINDSUBTITLES',
silent=not USE_SUBTITLES) silent=not USE_SUBTITLES)
showList = []
background_mapping_task = threading.Thread(name='LOAD-MAPPINGS', target=indexermapper.load_mapped_ids) background_mapping_task = threading.Thread(name='LOAD-MAPPINGS', target=indexermapper.load_mapped_ids)
watchedStateQueueScheduler = scheduler.Scheduler( watchedStateQueueScheduler = scheduler.Scheduler(
@ -1472,23 +1522,6 @@ def initialize(console_logging=True):
run_delay=datetime.timedelta(minutes=5), run_delay=datetime.timedelta(minutes=5),
threadName='PLEXWATCHEDSTATE') threadName='PLEXWATCHEDSTATE')
try:
import _scandir
except ImportError:
_scandir = None
try:
import ctypes
except ImportError:
ctypes = None
if None is not _scandir and None is not ctypes and not getattr(_scandir, 'DirEntry', None):
MODULE_UPDATE_STRING = \
'Your scandir binary module is outdated, using the slow but newer Python module.' \
'<br>Upgrade the binary at a command prompt with' \
' # <span class="boldest">python -m pip install -U scandir</span>' \
'<br>Important: You <span class="boldest">must</span> Shutdown SickGear before upgrading'
__INITIALIZED__ = True __INITIALIZED__ = True
return True return True
@ -1935,7 +1968,8 @@ def save_config():
# new_config['Pushalot']['pushalot_authorizationtoken'] = PUSHALOT_AUTHORIZATIONTOKEN # new_config['Pushalot']['pushalot_authorizationtoken'] = PUSHALOT_AUTHORIZATIONTOKEN
('Trakt', [ ('Trakt', [
('use_%s', int(USE_TRAKT)), ('use_%s', int(USE_TRAKT)),
('update_collection', TRAKT_UPDATE_COLLECTION and trakt_helpers.build_config_string(TRAKT_UPDATE_COLLECTION)), ('update_collection', TRAKT_UPDATE_COLLECTION
and trakt_helpers.build_config_string(TRAKT_UPDATE_COLLECTION)),
('accounts', TraktAPI.build_config_string(TRAKT_ACCOUNTS)), ('accounts', TraktAPI.build_config_string(TRAKT_ACCOUNTS)),
('mru', TRAKT_MRU), ('mru', TRAKT_MRU),
# new_config['Trakt'] = {} # new_config['Trakt'] = {}

35
sickbeard/classes.py

@ -23,6 +23,8 @@ from unidecode import unidecode
import datetime import datetime
import os import os
import re import re
import threading
import copy
import sickbeard import sickbeard
@ -466,3 +468,36 @@ class SimpleNamespace:
def __eq__(self, other): def __eq__(self, other):
return self.__dict__ == other.__dict__ 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) 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): class MainSanityCheck(db.DBSanityCheck):
def check(self): def check(self):
self.fix_missing_table_indexes() 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') 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: for cur_ep in ep_results:
if not cur_ep['location']: if not cur_ep['location']:
continue 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 # 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') 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: for cur_result in history_results:
# find the associated download, if there isn't one then ignore it # find the associated download, if there isn't one then ignore it
download_results = self.connection.select( download_results = self.connection.select(
@ -346,7 +350,7 @@ class AddSizeAndSceneNameFields(db.SchemaUpgrade):
' WHERE provider = -1 AND showid = ? AND season = ? AND episode = ? AND date > ?', ' WHERE provider = -1 AND showid = ? AND season = ? AND episode = ? AND date > ?',
[cur_result['showid'], cur_result['season'], cur_result['episode'], cur_result['date']]) [cur_result['showid'], cur_result['season'], cur_result['episode'], cur_result['date']])
if not download_results: 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) 'resource'] + ' but couldn\'t find the associated download, skipping it', logger.DEBUG)
continue 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 # 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 = ""') 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: for cur_result in empty_results:
ep_file_name = ek.ek(os.path.basename, cur_result['location']) ep_file_name = ek.ek(os.path.basename, cur_result['location'])
@ -541,7 +545,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
common.Quality.UNKNOWN], []) common.Quality.UNKNOWN], [])
# update qualities (including templates) # 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 = [] cl = []
shows = self.connection.select('SELECT * FROM tv_shows') shows = self.connection.select('SELECT * FROM tv_shows')
for cur_show in shows: for cur_show in shows:
@ -556,7 +560,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
# update status that are are within the old hdwebdl # update status that are are within the old hdwebdl
# (1<<3 which is 8) and better -- exclude unknown (1<<15 which is 32768) # (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 = [] cl = []
episodes = self.connection.select('SELECT * FROM tv_episodes WHERE status < 3276800 AND status >= 800') episodes = self.connection.select('SELECT * FROM tv_episodes WHERE status < 3276800 AND status >= 800')
for cur_episode in episodes: for cur_episode in episodes:
@ -568,7 +572,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
# may not always coordinate together # may not always coordinate together
# update previous history so it shows the correct action # 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 = [] cl = []
history_action = self.connection.select('SELECT * FROM history WHERE action < 3276800 AND action >= 800') history_action = self.connection.select('SELECT * FROM history WHERE action < 3276800 AND action >= 800')
for cur_entry in history_action: for cur_entry in history_action:
@ -577,7 +581,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
self.connection.mass_action(cl) self.connection.mass_action(cl)
# update previous history so it shows the correct quality # 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 = [] cl = []
history_quality = self.connection.select('SELECT * FROM history WHERE quality < 32768 AND quality >= 8') history_quality = self.connection.select('SELECT * FROM history WHERE quality < 32768 AND quality >= 8')
for cur_entry in history_quality: for cur_entry in history_quality:
@ -588,7 +592,7 @@ class Add1080pAndRawHDQualities(db.SchemaUpgrade):
self.incDBVersion() self.incDBVersion()
# cleanup and reduce db if any previous data was removed # 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') self.connection.action('VACUUM')
return self.checkDBVersion() return self.checkDBVersion()
@ -600,10 +604,10 @@ class AddShowidTvdbidIndex(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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') 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'): if not self.hasTable('idx_showid'):
self.connection.action('CREATE INDEX idx_showid ON tv_episodes (showid);') self.connection.action('CREATE INDEX idx_showid ON tv_episodes (showid);')
if not self.hasTable('idx_tvdb_id'): if not self.hasTable('idx_tvdb_id'):
@ -619,7 +623,7 @@ class AddLastUpdateTVDB(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasColumn('tv_shows', 'last_update_tvdb'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'last_update_tvdb', default=1) self.addColumn('tv_shows', 'last_update_tvdb', default=1)
@ -632,7 +636,7 @@ class AddDBIncreaseTo15(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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() self.incDBVersion()
return self.checkDBVersion() return self.checkDBVersion()
@ -643,7 +647,7 @@ class AddIMDbInfo(db.SchemaUpgrade):
db_backed_up = False db_backed_up = False
if not self.hasTable('imdb_info'): 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.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True db_backed_up = True
@ -653,7 +657,7 @@ class AddIMDbInfo(db.SchemaUpgrade):
' rating TEXT, votes INTEGER, last_update NUMERIC)') ' rating TEXT, votes INTEGER, last_update NUMERIC)')
if not self.hasColumn('tv_shows', 'imdb_id'): 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: if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
@ -674,7 +678,7 @@ class AddProperNamingSupport(db.SchemaUpgrade):
return self.checkDBVersion() return self.checkDBVersion()
if not self.hasColumn('tv_episodes', 'is_proper'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_episodes', 'is_proper') self.addColumn('tv_episodes', 'is_proper')
@ -694,7 +698,7 @@ class AddEmailSubscriptionTable(db.SchemaUpgrade):
return self.checkDBVersion() return self.checkDBVersion()
if not self.hasColumn('tv_shows', 'notify_list'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'notify_list', 'TEXT', None) self.addColumn('tv_shows', 'notify_list', 'TEXT', None)
@ -718,7 +722,7 @@ class AddProperSearch(db.SchemaUpgrade):
return self.checkDBVersion() return self.checkDBVersion()
if not self.hasColumn('info', 'last_proper_search'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('info', 'last_proper_search', default=1) self.addColumn('info', 'last_proper_search', default=1)
@ -730,7 +734,7 @@ class AddProperSearch(db.SchemaUpgrade):
class AddDvdOrderOption(db.SchemaUpgrade): class AddDvdOrderOption(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasColumn('tv_shows', 'dvdorder'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'dvdorder', 'NUMERIC', '0') self.addColumn('tv_shows', 'dvdorder', 'NUMERIC', '0')
@ -742,7 +746,7 @@ class AddDvdOrderOption(db.SchemaUpgrade):
class AddSubtitlesSupport(db.SchemaUpgrade): class AddSubtitlesSupport(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasColumn('tv_shows', 'subtitles'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'subtitles') self.addColumn('tv_shows', 'subtitles')
self.addColumn('tv_episodes', 'subtitles', 'TEXT', '') self.addColumn('tv_episodes', 'subtitles', 'TEXT', '')
@ -758,10 +762,10 @@ class ConvertTVShowsToIndexerScheme(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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'): 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('DROP TABLE tmp_tv_shows')
self.connection.action('ALTER TABLE tv_shows RENAME TO 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): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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'): 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('DROP TABLE tmp_tv_episodes')
self.connection.action('ALTER TABLE tv_episodes RENAME TO 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): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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'): 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('DROP TABLE tmp_imdb_info')
self.connection.action('ALTER TABLE imdb_info RENAME TO 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): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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'): 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('DROP TABLE tmp_info')
self.connection.action('ALTER TABLE info RENAME TO 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
if not self.hasColumn('tv_shows', 'archive_firstmatch'): 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.addColumn('tv_shows', 'archive_firstmatch', 'NUMERIC', '0')
self.incDBVersion() self.incDBVersion()
@ -896,7 +900,7 @@ class AddSceneNumbering(db.SchemaUpgrade):
if self.hasTable('scene_numbering'): if self.hasTable('scene_numbering'):
self.connection.action('DROP TABLE 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( self.connection.action(
'CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER,' 'CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER,'
' scene_season INTEGER, scene_episode INTEGER,' ' scene_season INTEGER, scene_episode INTEGER,'
@ -912,7 +916,7 @@ class ConvertIndexerToInteger(db.SchemaUpgrade):
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
cl = [] 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) = ?', ['1', 'tvdb']])
cl.append(['UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?', ['2', 'tvrage']]) cl.append(['UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?', ['2', 'tvrage']])
cl.append(['UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?', ['1', 'tvdb']]) cl.append(['UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?', ['1', 'tvdb']])
@ -936,13 +940,13 @@ class AddRequireAndIgnoreWords(db.SchemaUpgrade):
db_backed_up = False db_backed_up = False
if not self.hasColumn('tv_shows', 'rls_require_words'): 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.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True db_backed_up = True
self.addColumn('tv_shows', 'rls_require_words', 'TEXT', '') self.addColumn('tv_shows', 'rls_require_words', 'TEXT', '')
if not self.hasColumn('tv_shows', 'rls_ignore_words'): 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: if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'rls_ignore_words', 'TEXT', '') self.addColumn('tv_shows', 'rls_ignore_words', 'TEXT', '')
@ -956,14 +960,14 @@ class AddSportsOption(db.SchemaUpgrade):
def execute(self): def execute(self):
db_backed_up = False db_backed_up = False
if not self.hasColumn('tv_shows', 'sports'): 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.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True db_backed_up = True
self.addColumn('tv_shows', 'sports', 'NUMERIC', '0') self.addColumn('tv_shows', 'sports', 'NUMERIC', '0')
if self.hasColumn('tv_shows', 'air_by_date') and self.hasColumn('tv_shows', 'sports'): if self.hasColumn('tv_shows', 'air_by_date') and self.hasColumn('tv_shows', 'sports'):
# update sports column # 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: if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
cl = [] cl = []
@ -984,7 +988,7 @@ class AddSceneNumberingToTvEpisodes(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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_season', 'NUMERIC', 'NULL')
self.addColumn('tv_episodes', 'scene_episode', 'NUMERIC', 'NULL') self.addColumn('tv_episodes', 'scene_episode', 'NUMERIC', 'NULL')
@ -997,7 +1001,7 @@ class AddAnimeTVShow(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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.addColumn('tv_shows', 'anime', 'NUMERIC', '0')
self.incDBVersion() self.incDBVersion()
@ -1009,7 +1013,7 @@ class AddAbsoluteNumbering(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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.addColumn('tv_episodes', 'absolute_number', 'NUMERIC', '0')
self.incDBVersion() self.incDBVersion()
@ -1021,7 +1025,7 @@ class AddSceneAbsoluteNumbering(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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', 'absolute_number', 'NUMERIC', '0')
self.addColumn('scene_numbering', 'scene_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)'], cl = [['CREATE TABLE blacklist (show_id INTEGER, range TEXT, keyword TEXT)'],
['CREATE TABLE whitelist (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.connection.mass_action(cl)
self.incDBVersion() self.incDBVersion()
@ -1048,7 +1052,7 @@ class AddSceneAbsoluteNumbering2(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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.addColumn('tv_episodes', 'scene_absolute_number', 'NUMERIC', '0')
self.incDBVersion() self.incDBVersion()
@ -1060,7 +1064,7 @@ class AddXemRefresh(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Creating table xem_refresh') upgrade_log(u'Creating table xem_refresh')
self.connection.action( self.connection.action(
'CREATE TABLE xem_refresh (indexer TEXT, indexer_id INTEGER PRIMARY KEY, last_refreshed INTEGER)') '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): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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.addColumn('tv_shows', 'scene', 'NUMERIC', '0')
self.incDBVersion() self.incDBVersion()
@ -1088,7 +1092,7 @@ class AddIndexerMapping(db.SchemaUpgrade):
if self.hasTable('indexer_mapping'): if self.hasTable('indexer_mapping'):
self.connection.action('DROP TABLE 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( self.connection.action(
'CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER, mindexer NUMERIC,' 'CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER, mindexer NUMERIC,'
' PRIMARY KEY (indexer_id, indexer))') ' PRIMARY KEY (indexer_id, indexer))')
@ -1102,11 +1106,11 @@ class AddVersionToTvEpisodes(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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', 'release_group', 'TEXT', '')
self.addColumn('tv_episodes', 'version', 'NUMERIC', '-1') 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.addColumn('history', 'version', 'NUMERIC', '-1')
self.incDBVersion() self.incDBVersion()
@ -1118,7 +1122,7 @@ class BumpDatabaseVersion(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version') upgrade_log(u'Bumping database version')
self.setDBVersion(10000) self.setDBVersion(10000)
return self.checkDBVersion() return self.checkDBVersion()
@ -1129,7 +1133,7 @@ class Migrate41(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version') upgrade_log(u'Bumping database version')
self.setDBVersion(10001) self.setDBVersion(10001)
return self.checkDBVersion() return self.checkDBVersion()
@ -1145,7 +1149,7 @@ class Migrate43(db.SchemaUpgrade):
if self.hasTable(table): if self.hasTable(table):
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True 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) self.connection.action('DROP TABLE [%s]' % table)
db_chg = True db_chg = True
@ -1153,7 +1157,7 @@ class Migrate43(db.SchemaUpgrade):
if not db_backed_up: if not db_backed_up:
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
db_backed_up = True 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') self.dropColumn('tv_shows', 'tmdb_id')
db_chg = True db_chg = True
@ -1165,7 +1169,7 @@ class Migrate43(db.SchemaUpgrade):
self.connection.action('INSERT INTO db_version (db_version) VALUES (0);') self.connection.action('INSERT INTO db_version (db_version) VALUES (0);')
if not db_chg: if not db_chg:
logger.log(u'Bumping database version') upgrade_log(u'Bumping database version')
self.setDBVersion(10001) self.setDBVersion(10001)
return self.checkDBVersion() return self.checkDBVersion()
@ -1176,7 +1180,7 @@ class Migrate4301(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version') upgrade_log(u'Bumping database version')
self.setDBVersion(10002) self.setDBVersion(10002)
return self.checkDBVersion() return self.checkDBVersion()
@ -1187,7 +1191,7 @@ class Migrate4302(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
logger.log(u'Bumping database version') upgrade_log(u'Bumping database version')
self.setDBVersion(10003) self.setDBVersion(10003)
return self.checkDBVersion() return self.checkDBVersion()
@ -1198,7 +1202,7 @@ class MigrateUpstream(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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) self.setDBVersion(15)
return self.checkDBVersion() return self.checkDBVersion()
@ -1209,7 +1213,7 @@ class SickGearDatabaseVersion(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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) self.setDBVersion(20000)
return self.checkDBVersion() return self.checkDBVersion()
@ -1220,7 +1224,7 @@ class RemoveDefaultEpStatusFromTvShows(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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.dropColumn('tv_shows', 'default_ep_status')
self.setDBVersion(10000) self.setDBVersion(10000)
@ -1232,7 +1236,7 @@ class RemoveMinorDBVersion(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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.dropColumn('db_version', 'db_minor_version')
self.setDBVersion(10001) self.setDBVersion(10001)
@ -1243,7 +1247,7 @@ class RemoveMinorDBVersion(db.SchemaUpgrade):
class RemoveMetadataSub(db.SchemaUpgrade): class RemoveMetadataSub(db.SchemaUpgrade):
def execute(self): def execute(self):
if self.hasColumn('tv_shows', 'sub_use_sr_metadata'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.dropColumn('tv_shows', 'sub_use_sr_metadata') self.dropColumn('tv_shows', 'sub_use_sr_metadata')
@ -1256,10 +1260,10 @@ class DBIncreaseTo20001(db.SchemaUpgrade):
def execute(self): def execute(self):
db.backup_database('sickbeard.db', self.checkDBVersion()) 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') 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) self.setDBVersion(20001)
return self.checkDBVersion() return self.checkDBVersion()
@ -1269,7 +1273,7 @@ class DBIncreaseTo20001(db.SchemaUpgrade):
class AddTvShowOverview(db.SchemaUpgrade): class AddTvShowOverview(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasColumn('tv_shows', 'overview'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'overview', 'TEXT', '') self.addColumn('tv_shows', 'overview', 'TEXT', '')
@ -1281,7 +1285,7 @@ class AddTvShowOverview(db.SchemaUpgrade):
class AddTvShowTags(db.SchemaUpgrade): class AddTvShowTags(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasColumn('tv_shows', 'tag'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'tag', 'TEXT', 'Show List') self.addColumn('tv_shows', 'tag', 'TEXT', 'Show List')
@ -1298,7 +1302,7 @@ class ChangeMapIndexer(db.SchemaUpgrade):
if self.hasTable('indexer_mapping'): if self.hasTable('indexer_mapping'):
self.connection.action('DROP TABLE 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( self.connection.action(
'CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER NOT NULL,' '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,' ' 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)') self.connection.action('CREATE INDEX IF NOT EXISTS idx_mapping ON indexer_mapping (indexer_id, indexer)')
if not self.hasColumn('info', 'last_run_backlog'): 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) 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'): if self.hasTable('scene_exceptions_refresh'):
self.connection.action('DROP TABLE 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)') 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): class AddShowNotFoundCounter(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasTable('tv_shows_not_found'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.connection.action( self.connection.action(
@ -1369,7 +1373,7 @@ class AddShowNotFoundCounter(db.SchemaUpgrade):
class AddFlagTable(db.SchemaUpgrade): class AddFlagTable(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasTable('flags'): if not self.hasTable('flags'):
logger.log(u'Adding table flags') upgrade_log(u'Adding table flags')
db.backup_database('sickbeard.db', self.checkDBVersion()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.connection.action('CREATE TABLE flags (flag PRIMARY KEY NOT NULL)') self.connection.action('CREATE TABLE flags (flag PRIMARY KEY NOT NULL)')
@ -1382,7 +1386,7 @@ class AddFlagTable(db.SchemaUpgrade):
class DBIncreaseTo20007(db.SchemaUpgrade): class DBIncreaseTo20007(db.SchemaUpgrade):
def execute(self): def execute(self):
logger.log(u'Bumping database version') upgrade_log(u'Bumping database version')
self.setDBVersion(20007) self.setDBVersion(20007)
return self.checkDBVersion() return self.checkDBVersion()
@ -1407,7 +1411,7 @@ class AddWatched(db.SchemaUpgrade):
self.connection.action('VACUUM') self.connection.action('VACUUM')
if not self.hasTable('tv_episodes_watched'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.connection.action( self.connection.action(
@ -1425,7 +1429,7 @@ class AddPrune(db.SchemaUpgrade):
def execute(self): def execute(self):
if not self.hasColumn('tv_shows', 'prune'): 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()) db.backup_database('sickbeard.db', self.checkDBVersion())
self.addColumn('tv_shows', 'prune', 'INT', 0) self.addColumn('tv_shows', 'prune', 'INT', 0)

15
sickbeard/db.py

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

39
sickbeard/webserve.py

@ -104,6 +104,7 @@ class PageTemplate(Template):
self.sbThemeName = sickbeard.THEME_NAME self.sbThemeName = sickbeard.THEME_NAME
self.log_num_errors = len(classes.ErrorViewer.errors) self.log_num_errors = len(classes.ErrorViewer.errors)
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 = 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.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.sbPID = str(sickbeard.PID)
@ -627,6 +628,44 @@ class IsAliveHandler(BaseHandler):
self.write(results) 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): class WebHandler(BaseHandler):
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
super(BaseHandler, self).__init__(*arg, **kwargs) super(BaseHandler, self).__init__(*arg, **kwargs)

71
sickbeard/webserveInit.py

@ -7,8 +7,21 @@ import webapi
from sickbeard import logger from sickbeard import logger
from sickbeard.helpers import create_https_certificates, re_valid_hostname 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.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): class WebServer(threading.Thread):
@ -69,7 +82,7 @@ class WebServer(threading.Thread):
sickbeard.save_config() sickbeard.save_config()
# Load the app # Load the app
self.app = Application([], self.app = MyApplication([],
debug=True, debug=True,
serve_traceback=True, serve_traceback=True,
autoreload=False, autoreload=False,
@ -78,21 +91,56 @@ class WebServer(threading.Thread):
xsrf_cookies=True, xsrf_cookies=True,
login_url='%s/login/' % self.options['web_root']) login_url='%s/login/' % self.options['web_root'])
re_host_pattern = re_valid_hostname() self.re_host_pattern = re_valid_hostname()
self._add_loading_rules()
def _add_loading_rules(self):
# webui login/logout handlers # 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),
])
# 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(self.re_host_pattern, [
(r'%s/login(/?)' % self.options['web_root'], webserve.LoginHandler), (r'%s/login(/?)' % self.options['web_root'], webserve.LoginHandler),
(r'%s/logout(/?)' % self.options['web_root'], webserve.LogoutHandler), (r'%s/logout(/?)' % self.options['web_root'], webserve.LogoutHandler),
]) ])
# Web calendar handler (Needed because option Unprotected calendar) # 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), (r'%s/calendar' % self.options['web_root'], webserve.CalendarHandler),
]) ])
# Static File Handlers # Static File Handlers
self.app.add_handlers(re_host_pattern, [ self.app.add_handlers(self.re_host_pattern, [
# favicon # favicon
(r'%s/(favicon\.ico)' % self.options['web_root'], webserve.BaseStaticFileHandler, (r'%s/(favicon\.ico)' % self.options['web_root'], webserve.BaseStaticFileHandler,
{'path': os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}), {'path': os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}),
@ -119,7 +167,7 @@ class WebServer(threading.Thread):
]) ])
# Main Handler # 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/builder(/?)(.*)' % self.options['web_root'], webserve.ApiBuilder),
(r'%s/api(/?.*)' % self.options['web_root'], webapi.Api), (r'%s/api(/?.*)' % self.options['web_root'], webapi.Api),
(r'%s/imagecache(/?.*)' % self.options['web_root'], webserve.CachedImages), (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. # Ignore errors like 'ValueError: I/O operation on closed kqueue fd'. These might be thrown during a reload.
pass 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): def shut_down(self):
self.alive = False self.alive = False
if None is not self.io_loop: if None is not self.io_loop:

139
sickgear.py

@ -1,6 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python2
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
# #
# This file is part of SickGear. # 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 import db, logger, network_timezones, failed_history, name_cache
from sickbeard.tv import TVShow from sickbeard.tv import TVShow
from sickbeard.webserveInit import WebServer from sickbeard.webserveInit import WebServer
from sickbeard.databases.mainDB import MIN_DB_VERSION, MAX_DB_VERSION
from sickbeard.event_queue import Events from sickbeard.event_queue import Events
from sickbeard.exceptions import ex from sickbeard.exceptions import ex
from lib.configobj import ConfigObj from lib.configobj import ConfigObj
@ -158,13 +155,15 @@ class SickGear(object):
return '\n'.join(help_msg) return '\n'.join(help_msg)
@staticmethod @staticmethod
def execute_rollback(mo, max_v): def execute_rollback(mo, max_v, load_msg):
global rollback_loaded global rollback_loaded
try: try:
if None is rollback_loaded: if None is rollback_loaded:
rollback_loaded = db.get_rollback_module() rollback_loaded = db.get_rollback_module()
if None is not rollback_loaded: 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: else:
print(u'ERROR: Could not download Rollback Module.') print(u'ERROR: Could not download Rollback Module.')
except (StandardError, Exception): 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) print('or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING)
sys.exit(1) 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) self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)
# Rename the main thread # Rename the main thread
@ -339,60 +338,15 @@ class SickGear(object):
except (StandardError, Exception) as er: except (StandardError, Exception) as er:
print('Stack Size %s not set: %s' % (stack_size, er.message)) 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: if self.run_as_daemon:
self.daemonize() self.daemonize()
# Get PID # Get PID
sickbeard.PID = os.getpid() sickbeard.PID = os.getpid()
# Initialize the config
sickbeard.initialize(console_logging=self.console_logging)
if self.forced_port: if self.forced_port:
logger.log(u'Forcing web server to port %s' % self.forced_port) logger.log(u'Forcing web server to port %s' % self.forced_port)
self.start_port = self.forced_port self.start_port = self.forced_port
@ -440,6 +394,7 @@ class SickGear(object):
self.webserver = WebServer(self.web_options) self.webserver = WebServer(self.web_options)
self.webserver.start() self.webserver.start()
sickbeard.started = True
except (StandardError, Exception): except (StandardError, Exception):
logger.log(u'Unable to start web server, is something else running on port %d?' % self.start_port, logger.log(u'Unable to start web server, is something else running on port %d?' % self.start_port,
logger.ERROR) logger.ERROR)
@ -450,30 +405,95 @@ class SickGear(object):
sickbeard.launch_browser(self.start_port) sickbeard.launch_browser(self.start_port)
self.exit(1) 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 # Check if we need to perform a restore first
restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore') restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
if os.path.exists(restore_dir): if os.path.exists(restore_dir):
sickbeard.classes.loading_msg.message = 'Restoring files'
if self.restore(restore_dir, sickbeard.DATA_DIR): if self.restore(restore_dir, sickbeard.DATA_DIR):
logger.log(u'Restore successful...') logger.log(u'Restore successful...')
else: else:
logger.log_error_and_exit(u'Restore FAILED!') logger.log_error_and_exit(u'Restore FAILED!')
# Build from the DB to start with # Build from the DB to start with
sickbeard.classes.loading_msg.message = 'Loading shows from db'
self.load_shows_from_db() self.load_shows_from_db()
# Fire up all our threads # Fire up all our threads
sickbeard.classes.loading_msg.message = 'Starting threads'
sickbeard.start() sickbeard.start()
# Build internal name cache # Build internal name cache
sickbeard.classes.loading_msg.message = 'Build name cache'
name_cache.buildNameCache() name_cache.buildNameCache()
# refresh network timezones # refresh network timezones
sickbeard.classes.loading_msg.message = 'Checking network timezones'
network_timezones.update_network_dict() network_timezones.update_network_dict()
# load all ids from xem # 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 = threading.Thread(name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids)
startup_background_tasks.start() startup_background_tasks.start()
sickbeard.classes.loading_msg.message = 'Checking history'
# check history snatched_proper update # check history snatched_proper update
if not db.DBConnection().has_flag('history_snatch_proper'): if not db.DBConnection().has_flag('history_snatch_proper'):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@ -486,11 +506,16 @@ class SickGear(object):
# Start an update if we're supposed to # Start an update if we're supposed to
if self.force_update or sickbeard.UPDATE_SHOWS_ON_START: 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 sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable
# Launch browser sickbeard.classes.loading_msg.message = 'Switching to default web server'
if sickbeard.LAUNCH_BROWSER and not self.no_launch: time.sleep(2)
sickbeard.launch_browser(self.start_port) self.webserver.switch_handlers()
# # Launch browser
# if sickbeard.LAUNCH_BROWSER and not self.no_launch:
# sickbeard.launch_browser(self.start_port)
# main loop # main loop
while True: while True:

2
tests/compatibility_tests.py

@ -14,7 +14,7 @@ class CompatibilityTests(unittest.TestCase):
if x.endswith('.py'): if x.endswith('.py'):
pyfiles.append(os.path.join(dirpath, x)) 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' output = subprocess.Popen('2to3'
' -f except' ' -f except'

Loading…
Cancel
Save