2014-08-11 17:40:03 +00:00
|
|
|
#!/usr/bin/env python2
|
|
|
|
# Author: Nic Wolfe <nic@wolfeden.ca>
|
|
|
|
# URL: http://code.google.com/p/sickbeard/
|
|
|
|
#
|
2014-11-12 16:43:14 +00:00
|
|
|
# This file is part of SickGear.
|
2014-08-11 17:40:03 +00:00
|
|
|
#
|
2014-11-12 16:43:14 +00:00
|
|
|
# SickGear is free software: you can redistribute it and/or modify
|
2014-08-11 17:40:03 +00:00
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
2014-11-12 16:43:14 +00:00
|
|
|
# SickGear is distributed in the hope that it will be useful,
|
2014-08-11 17:40:03 +00:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
2014-11-12 16:43:14 +00:00
|
|
|
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Check needed software dependencies to nudge users to fix their setup
|
2015-06-09 11:13:00 +00:00
|
|
|
from __future__ import print_function
|
2014-08-11 17:40:03 +00:00
|
|
|
from __future__ import with_statement
|
|
|
|
|
2016-07-11 14:34:31 +00:00
|
|
|
import datetime
|
|
|
|
import errno
|
|
|
|
import getopt
|
|
|
|
import locale
|
|
|
|
import os
|
2014-08-11 17:40:03 +00:00
|
|
|
import signal
|
|
|
|
import sys
|
|
|
|
import shutil
|
|
|
|
import subprocess
|
2016-07-11 14:34:31 +00:00
|
|
|
import time
|
2015-02-20 14:03:27 +00:00
|
|
|
import threading
|
2017-08-23 17:39:30 +00:00
|
|
|
import warnings
|
|
|
|
|
|
|
|
warnings.filterwarnings('ignore', module=r'.*fuzzywuzzy.*')
|
|
|
|
warnings.filterwarnings('ignore', module=r'.*Cheetah.*')
|
2018-03-26 22:49:12 +00:00
|
|
|
warnings.filterwarnings('ignore', module=r'.*connectionpool.*', message='.*certificate verification.*')
|
|
|
|
warnings.filterwarnings('ignore', module=r'.*ssl_.*', message='.*SSLContext object.*')
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-08-23 14:11:56 +00:00
|
|
|
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.')
|
2014-08-11 17:40:03 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
2017-02-03 01:44:55 +00:00
|
|
|
try:
|
|
|
|
import _cleaner
|
|
|
|
except (StandardError, Exception):
|
|
|
|
pass
|
|
|
|
|
2014-08-11 17:40:03 +00:00
|
|
|
try:
|
|
|
|
import Cheetah
|
|
|
|
|
2017-10-13 01:12:09 +00:00
|
|
|
if Cheetah.Version[0] < '2':
|
2014-08-11 17:40:03 +00:00
|
|
|
raise ValueError
|
|
|
|
except ValueError:
|
2015-06-09 11:13:00 +00:00
|
|
|
print('Sorry, requires Python module Cheetah 2.1.0 or newer.')
|
2014-08-11 17:40:03 +00:00
|
|
|
sys.exit(1)
|
2018-03-29 16:23:33 +00:00
|
|
|
except (StandardError, Exception):
|
2015-06-09 11:13:00 +00:00
|
|
|
print('The Python module Cheetah is required')
|
2014-08-11 17:40:03 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
2015-06-04 13:36:38 +00:00
|
|
|
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib')))
|
2015-06-14 15:34:24 +00:00
|
|
|
from lib.six import moves
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# 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
|
2015-02-20 14:03:27 +00:00
|
|
|
from sickbeard import db, logger, network_timezones, failed_history, name_cache
|
2014-08-11 17:40:03 +00:00
|
|
|
from sickbeard.tv import TVShow
|
2014-11-13 05:36:47 +00:00
|
|
|
from sickbeard.webserveInit import WebServer
|
2014-08-11 17:40:03 +00:00
|
|
|
from sickbeard.databases.mainDB import MIN_DB_VERSION, MAX_DB_VERSION
|
|
|
|
from sickbeard.event_queue import Events
|
2016-07-11 14:34:31 +00:00
|
|
|
from sickbeard.exceptions import ex
|
2014-08-11 17:40:03 +00:00
|
|
|
from lib.configobj import ConfigObj
|
|
|
|
|
|
|
|
throwaway = datetime.datetime.strptime('20110101', '%Y%m%d')
|
2018-01-26 10:26:23 +00:00
|
|
|
rollback_loaded = None
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
signal.signal(signal.SIGINT, sickbeard.sig_handler)
|
|
|
|
signal.signal(signal.SIGTERM, sickbeard.sig_handler)
|
2016-07-11 14:34:31 +00:00
|
|
|
if 'win32' == sys.platform:
|
|
|
|
signal.signal(signal.SIGBREAK, sickbeard.sig_handler)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
|
2014-11-12 16:43:14 +00:00
|
|
|
class SickGear(object):
|
2014-08-11 17:40:03 +00:00
|
|
|
def __init__(self):
|
|
|
|
# system event callback for shutdown/restart
|
|
|
|
sickbeard.events = Events(self.shutdown)
|
|
|
|
|
|
|
|
# daemon constants
|
2016-11-05 20:28:19 +00:00
|
|
|
self.run_as_daemon = False
|
|
|
|
self.create_pid = False
|
|
|
|
self.pid_file = ''
|
|
|
|
|
|
|
|
self.run_as_systemd = False
|
|
|
|
self.console_logging = False
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# webserver constants
|
|
|
|
self.webserver = None
|
2016-11-05 20:28:19 +00:00
|
|
|
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
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2015-02-20 14:03:27 +00:00
|
|
|
@staticmethod
|
|
|
|
def help_message():
|
2014-08-11 17:40:03 +00:00
|
|
|
"""
|
|
|
|
print help message for commandline options
|
|
|
|
"""
|
2016-11-05 20:28:19 +00:00
|
|
|
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]
|
2014-08-11 17:40:03 +00:00
|
|
|
else:
|
2016-11-05 20:28:19 +00:00
|
|
|
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]
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
return '\n'.join(help_msg)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2018-01-26 10:26:23 +00:00
|
|
|
@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
|
|
|
|
|
2014-08-11 17:40:03 +00:00
|
|
|
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:
|
2015-02-20 14:03:27 +00:00
|
|
|
locale.setlocale(locale.LC_ALL, '')
|
2014-11-08 01:09:54 +00:00
|
|
|
except (locale.Error, IOError):
|
|
|
|
pass
|
|
|
|
try:
|
2014-08-11 17:40:03 +00:00
|
|
|
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'
|
|
|
|
|
2015-02-20 14:03:27 +00:00
|
|
|
if not hasattr(sys, 'setdefaultencoding'):
|
2015-06-14 15:34:24 +00:00
|
|
|
moves.reload_module(sys)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
# pylint: disable=E1101
|
2016-11-05 20:28:19 +00:00
|
|
|
# On non-unicode builds this raises an AttributeError, if encoding type is not valid it throws a LookupError
|
2014-08-11 17:40:03 +00:00
|
|
|
sys.setdefaultencoding(sickbeard.SYS_ENCODING)
|
2016-11-05 20:28:19 +00:00
|
|
|
except (StandardError, Exception):
|
2015-06-09 11:13:00 +00:00
|
|
|
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)
|
2014-08-11 17:40:03 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# Need console logging for SickBeard.py and SickBeard-console.exe
|
2016-11-05 20:28:19 +00:00
|
|
|
self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Rename the main thread
|
2015-02-20 14:03:27 +00:00
|
|
|
threading.currentThread().name = 'MAIN'
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
try:
|
2016-11-05 20:28:19 +00:00
|
|
|
opts, args = getopt.getopt(sys.argv[1:], 'hfqdsp::',
|
|
|
|
['help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'systemd', 'pidfile=',
|
|
|
|
'port=', 'datadir=', 'config=', 'noresize']) # @UnusedVariable
|
2014-08-11 17:40:03 +00:00
|
|
|
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'):
|
2016-11-05 20:28:19 +00:00
|
|
|
self.console_logging = False
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Should we update (from indexer) all shows in the DB right away?
|
|
|
|
if o in ('-f', '--forceupdate'):
|
2016-11-05 20:28:19 +00:00
|
|
|
self.force_update = True
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Suppress launching web browser
|
|
|
|
# Needed for OSes without default browser assigned
|
|
|
|
# Prevent duplicate browser window when restarting in the app
|
|
|
|
if o in ('--nolaunch',):
|
2016-11-05 20:28:19 +00:00
|
|
|
self.no_launch = True
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Override default/configured port
|
|
|
|
if o in ('-p', '--port'):
|
|
|
|
try:
|
2016-11-05 20:28:19 +00:00
|
|
|
self.forced_port = int(a)
|
2014-08-11 17:40:03 +00:00
|
|
|
except ValueError:
|
2015-02-20 14:03:27 +00:00
|
|
|
sys.exit('Port: %s is not a number. Exiting.' % a)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Run as a double forked daemon
|
|
|
|
if o in ('-d', '--daemon'):
|
2016-11-05 20:28:19 +00:00
|
|
|
self.run_as_daemon = True
|
2016-11-14 21:33:15 +00:00
|
|
|
# When running as daemon disable console_logging and don't start browser
|
2016-11-05 20:28:19 +00:00
|
|
|
self.console_logging = False
|
|
|
|
self.no_launch = True
|
|
|
|
|
|
|
|
if 'win32' == sys.platform:
|
|
|
|
self.run_as_daemon = False
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
# 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
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Write a pidfile if requested
|
|
|
|
if o in ('--pidfile',):
|
2016-11-05 20:28:19 +00:00
|
|
|
self.create_pid = True
|
|
|
|
self.pid_file = str(a)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# If the pidfile already exists, sickbeard may still be running, so exit
|
2016-11-05 20:28:19 +00:00
|
|
|
if os.path.exists(self.pid_file):
|
|
|
|
sys.exit('PID file: %s already exists. Exiting.' % self.pid_file)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# 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
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.create_pid:
|
|
|
|
if self.run_as_daemon:
|
|
|
|
pid_dir = os.path.dirname(self.pid_file)
|
2014-08-11 17:40:03 +00:00
|
|
|
if not os.access(pid_dir, os.F_OK):
|
2014-12-08 12:17:45 +00:00
|
|
|
sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir)
|
2014-08-11 17:40:03 +00:00
|
|
|
if not os.access(pid_dir, os.W_OK):
|
2014-12-08 12:17:45 +00:00
|
|
|
sys.exit(u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
else:
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.console_logging:
|
2015-06-09 11:13:00 +00:00
|
|
|
print(u'Not running in daemon mode. PID file creation disabled')
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
self.create_pid = False
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# If they don't specify a config file then put it in the data dir
|
|
|
|
if not sickbeard.CONFIG_FILE:
|
2015-02-20 14:03:27 +00:00
|
|
|
sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini')
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Make sure that we can create the data dir
|
|
|
|
if not os.access(sickbeard.DATA_DIR, os.F_OK):
|
|
|
|
try:
|
2015-06-09 22:32:13 +00:00
|
|
|
os.makedirs(sickbeard.DATA_DIR, 0o744)
|
2014-12-08 12:17:45 +00:00
|
|
|
except os.error:
|
2015-02-20 14:03:27 +00:00
|
|
|
sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Make sure we can write to the data dir
|
|
|
|
if not os.access(sickbeard.DATA_DIR, os.W_OK):
|
2014-12-08 12:17:45 +00:00
|
|
|
sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# 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):
|
2014-12-08 12:17:45 +00:00
|
|
|
sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE)
|
2014-08-11 17:40:03 +00:00
|
|
|
elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK):
|
2014-12-08 12:17:45 +00:00
|
|
|
sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting'
|
|
|
|
% os.path.dirname(sickbeard.CONFIG_FILE))
|
2014-08-11 17:40:03 +00:00
|
|
|
os.chdir(sickbeard.DATA_DIR)
|
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.console_logging:
|
2015-06-09 11:13:00 +00:00
|
|
|
print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE)
|
2014-12-08 12:17:45 +00:00
|
|
|
|
2014-08-11 17:40:03 +00:00
|
|
|
# Load the config and publish it to the sickbeard package
|
|
|
|
if not os.path.isfile(sickbeard.CONFIG_FILE):
|
2015-06-09 11:13:00 +00:00
|
|
|
print(u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
|
2017-07-25 21:47:12 +00:00
|
|
|
try:
|
|
|
|
stack_size = int(sickbeard.CFG['General']['stack_size'])
|
2018-03-29 16:23:33 +00:00
|
|
|
except (StandardError, Exception):
|
2017-07-25 21:47:12 +00:00
|
|
|
stack_size = None
|
|
|
|
|
|
|
|
if stack_size:
|
|
|
|
try:
|
|
|
|
threading.stack_size(stack_size)
|
2018-03-29 16:23:33 +00:00
|
|
|
except (StandardError, Exception) as er:
|
|
|
|
print('Stack Size %s not set: %s' % (stack_size, er.message))
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-09-13 18:50:05 +00:00
|
|
|
# check all db versions
|
2018-01-26 10:26:23 +00:00
|
|
|
for d, min_v, max_v, base_v, mo in [
|
2018-03-29 16:23:33 +00:00
|
|
|
('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')
|
2016-09-13 18:50:05 +00:00
|
|
|
]:
|
|
|
|
cur_db_version = db.DBConnection(d).checkDBVersion()
|
|
|
|
|
2018-01-26 10:26:23 +00:00
|
|
|
# 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:
|
2016-09-13 18:50:05 +00:00
|
|
|
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))
|
2018-01-26 10:26:23 +00:00
|
|
|
self.execute_rollback(mo, max_v)
|
2016-09-13 18:50:05 +00:00
|
|
|
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)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2018-01-26 10:26:23 +00:00
|
|
|
# free memory
|
|
|
|
global rollback_loaded
|
|
|
|
rollback_loaded = None
|
|
|
|
|
2014-08-11 17:40:03 +00:00
|
|
|
# Initialize the config and our threads
|
2016-11-05 20:28:19 +00:00
|
|
|
sickbeard.initialize(console_logging=self.console_logging)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.run_as_daemon:
|
2014-08-11 17:40:03 +00:00
|
|
|
self.daemonize()
|
|
|
|
|
|
|
|
# Get PID
|
|
|
|
sickbeard.PID = os.getpid()
|
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.forced_port:
|
|
|
|
logger.log(u'Forcing web server to port %s' % self.forced_port)
|
|
|
|
self.start_port = self.forced_port
|
2014-08-11 17:40:03 +00:00
|
|
|
else:
|
2016-11-05 20:28:19 +00:00
|
|
|
self.start_port = sickbeard.WEB_PORT
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
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:
|
2017-02-06 13:43:06 +00:00
|
|
|
self.webhost = (('0.0.0.0', '::')[sickbeard.WEB_IPV6], '')[sickbeard.WEB_IPV64]
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# web server options
|
2018-03-29 16:23:33 +00:00
|
|
|
self.web_options = dict(
|
|
|
|
host=self.webhost,
|
|
|
|
port=int(self.start_port),
|
|
|
|
web_root=sickbeard.WEB_ROOT,
|
|
|
|
data_root=os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME),
|
|
|
|
log_dir=self.log_dir,
|
|
|
|
username=sickbeard.WEB_USERNAME,
|
|
|
|
password=sickbeard.WEB_PASSWORD,
|
|
|
|
handle_reverse_proxy=sickbeard.HANDLE_REVERSE_PROXY,
|
|
|
|
enable_https=False,
|
|
|
|
https_cert=None,
|
|
|
|
https_key=None,
|
|
|
|
)
|
|
|
|
if sickbeard.ENABLE_HTTPS:
|
|
|
|
self.web_options.update(dict(
|
|
|
|
enable_https=sickbeard.ENABLE_HTTPS,
|
|
|
|
https_cert=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
|
|
|
|
https_key=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY)
|
|
|
|
))
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# start web server
|
|
|
|
try:
|
2014-12-08 12:17:45 +00:00
|
|
|
# used to check if existing SG instances have been started
|
2017-02-06 13:43:06 +00:00
|
|
|
sickbeard.helpers.wait_for_free_port(
|
|
|
|
sickbeard.WEB_IPV6 and '::1' or self.web_options['host'], self.web_options['port'])
|
2014-12-08 12:17:45 +00:00
|
|
|
|
2014-11-13 05:36:47 +00:00
|
|
|
self.webserver = WebServer(self.web_options)
|
2014-08-11 17:40:03 +00:00
|
|
|
self.webserver.start()
|
2016-11-05 20:28:19 +00:00
|
|
|
except (StandardError, Exception):
|
|
|
|
logger.log(u'Unable to start web server, is something else running on port %d?' % self.start_port,
|
2014-08-11 17:40:03 +00:00
|
|
|
logger.ERROR)
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.run_as_systemd:
|
|
|
|
self.exit(0)
|
|
|
|
if sickbeard.LAUNCH_BROWSER and not self.no_launch:
|
2015-02-20 14:03:27 +00:00
|
|
|
logger.log(u'Launching browser and exiting', logger.ERROR)
|
2016-11-05 20:28:19 +00:00
|
|
|
sickbeard.launch_browser(self.start_port)
|
|
|
|
self.exit(1)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2014-12-08 12:17:45 +00:00
|
|
|
# Check if we need to perform a restore first
|
2016-11-05 20:28:19 +00:00
|
|
|
restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
|
|
|
|
if os.path.exists(restore_dir):
|
|
|
|
if self.restore(restore_dir, sickbeard.DATA_DIR):
|
2015-02-20 14:03:27 +00:00
|
|
|
logger.log(u'Restore successful...')
|
2014-12-08 12:17:45 +00:00
|
|
|
else:
|
2015-02-20 14:03:27 +00:00
|
|
|
logger.log_error_and_exit(u'Restore FAILED!')
|
2014-12-08 12:17:45 +00:00
|
|
|
|
|
|
|
# Build from the DB to start with
|
2016-11-05 20:28:19 +00:00
|
|
|
self.load_shows_from_db()
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Fire up all our threads
|
|
|
|
sickbeard.start()
|
|
|
|
|
|
|
|
# Build internal name cache
|
|
|
|
name_cache.buildNameCache()
|
|
|
|
|
|
|
|
# refresh network timezones
|
|
|
|
network_timezones.update_network_dict()
|
|
|
|
|
2015-07-25 09:19:46 +00:00
|
|
|
# load all ids from xem
|
|
|
|
startup_background_tasks = threading.Thread(name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids)
|
|
|
|
startup_background_tasks.start()
|
|
|
|
|
2017-09-13 17:18:59 +00:00
|
|
|
# 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()
|
|
|
|
|
2014-08-11 17:40:03 +00:00
|
|
|
if sickbeard.USE_FAILED_DOWNLOADS:
|
2017-02-17 03:16:51 +00:00
|
|
|
failed_history.remove_old_history()
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Start an update if we're supposed to
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.force_update or sickbeard.UPDATE_SHOWS_ON_START:
|
2014-08-11 17:40:03 +00:00
|
|
|
sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable
|
|
|
|
|
|
|
|
# Launch browser
|
2016-11-05 20:28:19 +00:00
|
|
|
if sickbeard.LAUNCH_BROWSER and not self.no_launch:
|
|
|
|
sickbeard.launch_browser(self.start_port)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# main loop
|
2015-02-20 14:03:27 +00:00
|
|
|
while True:
|
2014-08-11 17:40:03 +00:00
|
|
|
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:
|
2016-11-05 20:28:19 +00:00
|
|
|
self.exit(0)
|
|
|
|
except OSError as er:
|
|
|
|
sys.stderr.write('fork #1 failed: %d (%s)\n' % (er.errno, er.strerror))
|
2014-08-11 17:40:03 +00:00
|
|
|
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:
|
2016-11-05 20:28:19 +00:00
|
|
|
self.exit(0)
|
|
|
|
except OSError as er:
|
|
|
|
sys.stderr.write('fork #2 failed: %d (%s)\n' % (er.errno, er.strerror))
|
2014-08-11 17:40:03 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# Write pid
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.create_pid:
|
2014-08-11 17:40:03 +00:00
|
|
|
pid = str(os.getpid())
|
2016-11-05 20:28:19 +00:00
|
|
|
logger.log(u'Writing PID: %s to %s' % (pid, self.pid_file))
|
2014-08-11 17:40:03 +00:00
|
|
|
try:
|
2016-11-05 20:28:19 +00:00
|
|
|
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))
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# Redirect all output
|
|
|
|
sys.stdout.flush()
|
|
|
|
sys.stderr.flush()
|
|
|
|
|
|
|
|
devnull = getattr(os, 'devnull', '/dev/null')
|
2015-06-14 03:49:23 +00:00
|
|
|
stdin = open(devnull, 'r')
|
|
|
|
stdout = open(devnull, 'a+')
|
|
|
|
stderr = open(devnull, 'a+')
|
2014-08-11 17:40:03 +00:00
|
|
|
os.dup2(stdin.fileno(), sys.stdin.fileno())
|
|
|
|
os.dup2(stdout.fileno(), sys.stdout.fileno())
|
|
|
|
os.dup2(stderr.fileno(), sys.stderr.fileno())
|
|
|
|
|
2015-02-20 14:03:27 +00:00
|
|
|
@staticmethod
|
2016-11-05 20:28:19 +00:00
|
|
|
def remove_pid_file(pidfile):
|
2014-08-11 17:40:03 +00:00
|
|
|
try:
|
2016-11-05 20:28:19 +00:00
|
|
|
if os.path.exists(pidfile):
|
|
|
|
os.remove(pidfile)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
except (IOError, OSError):
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2015-02-20 14:03:27 +00:00
|
|
|
@staticmethod
|
2016-11-05 20:28:19 +00:00
|
|
|
def load_shows_from_db():
|
2014-08-11 17:40:03 +00:00
|
|
|
"""
|
|
|
|
Populates the showList with shows from the database
|
|
|
|
"""
|
|
|
|
|
2015-02-20 14:03:27 +00:00
|
|
|
logger.log(u'Loading initial show list')
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
my_db = db.DBConnection()
|
|
|
|
sql_results = my_db.select('SELECT * FROM tv_shows')
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
sickbeard.showList = []
|
2016-11-05 20:28:19 +00:00
|
|
|
for sqlShow in sql_results:
|
2014-08-11 17:40:03 +00:00
|
|
|
try:
|
2016-11-05 20:28:19 +00:00
|
|
|
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):
|
2014-08-11 17:40:03 +00:00
|
|
|
try:
|
2016-11-05 20:28:19 +00:00
|
|
|
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)
|
2014-08-11 17:40:03 +00:00
|
|
|
return True
|
2016-11-05 20:28:19 +00:00
|
|
|
except (StandardError, Exception):
|
2014-08-11 17:40:03 +00:00
|
|
|
return False
|
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
def shutdown(self, ev_type):
|
2014-08-11 17:40:03 +00:00
|
|
|
if sickbeard.started:
|
|
|
|
# stop all tasks
|
|
|
|
sickbeard.halt()
|
|
|
|
|
|
|
|
# save all shows to DB
|
2015-09-18 00:06:34 +00:00
|
|
|
sickbeard.save_all()
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
# shutdown web server
|
|
|
|
if self.webserver:
|
2015-02-20 14:03:27 +00:00
|
|
|
logger.log('Shutting down Tornado')
|
2018-03-29 16:23:33 +00:00
|
|
|
self.webserver.shut_down()
|
2014-08-11 17:40:03 +00:00
|
|
|
try:
|
|
|
|
self.webserver.join(10)
|
2016-11-05 20:28:19 +00:00
|
|
|
except (StandardError, Exception):
|
2014-08-11 17:40:03 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
# if run as daemon delete the pidfile
|
2016-11-05 20:28:19 +00:00
|
|
|
if self.run_as_daemon and self.create_pid:
|
|
|
|
self.remove_pid_file(self.pid_file)
|
|
|
|
|
|
|
|
if sickbeard.events.SystemEvent.RESTART == ev_type:
|
2014-08-11 17:40:03 +00:00
|
|
|
|
|
|
|
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
|
2016-11-05 20:28:19 +00:00
|
|
|
|
|
|
|
if self.run_as_systemd:
|
|
|
|
logger.log(u'Restarting SickGear with exit(1) handler and %s' % popen_list)
|
|
|
|
logger.close()
|
|
|
|
self.exit(1)
|
|
|
|
|
2014-08-11 17:40:03 +00:00
|
|
|
if '--nolaunch' not in popen_list:
|
|
|
|
popen_list += ['--nolaunch']
|
2015-02-20 14:03:27 +00:00
|
|
|
logger.log(u'Restarting SickGear with %s' % popen_list)
|
2014-08-11 17:40:03 +00:00
|
|
|
logger.close()
|
|
|
|
subprocess.Popen(popen_list, cwd=os.getcwd())
|
|
|
|
|
|
|
|
# system exit
|
2016-11-05 20:28:19 +00:00
|
|
|
self.exit(0)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2016-11-05 20:28:19 +00:00
|
|
|
@staticmethod
|
|
|
|
def exit(code):
|
|
|
|
os._exit(code)
|
2014-08-11 17:40:03 +00:00
|
|
|
|
2018-03-29 16:23:33 +00:00
|
|
|
|
2015-02-20 14:03:27 +00:00
|
|
|
if __name__ == '__main__':
|
2014-08-11 17:40:03 +00:00
|
|
|
if sys.hexversion >= 0x020600F0:
|
|
|
|
freeze_support()
|
2016-07-11 14:34:31 +00:00
|
|
|
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))
|