1
0
mirror of https://github.com/moparisthebest/SickRage synced 2025-01-07 11:58:01 -05:00

Fixed startup/restart/shutdown issues on Windows, Linux, FreeBSD platforms tested.

Fixed for updating issues.

Fixed high cpu and memory usage.
This commit is contained in:
echel0n 2014-07-02 11:51:14 -07:00
parent 1fc909299d
commit 12ac388dc2
20 changed files with 482 additions and 454 deletions

View File

@ -20,8 +20,10 @@
# Check needed software dependencies to nudge users to fix their setup # Check needed software dependencies to nudge users to fix their setup
from __future__ import with_statement from __future__ import with_statement
import time
import sys import sys
import shutil import shutil
import subprocess
if sys.version_info < (2, 6): if sys.version_info < (2, 6):
print "Sorry, requires Python 2.6 or 2.7." print "Sorry, requires Python 2.6 or 2.7."
@ -59,13 +61,12 @@ import sickbeard
from sickbeard import db from sickbeard import db
from sickbeard.tv import TVShow from sickbeard.tv import TVShow
from sickbeard import logger from sickbeard import logger
from sickbeard import webserveInit from sickbeard.webserveInit import SRWebServer
from sickbeard.version import SICKBEARD_VERSION from sickbeard.version import SICKBEARD_VERSION
from sickbeard.databases.mainDB import MIN_DB_VERSION from sickbeard.databases.mainDB import MIN_DB_VERSION
from sickbeard.databases.mainDB import MAX_DB_VERSION from sickbeard.databases.mainDB import MAX_DB_VERSION
from lib.configobj import ConfigObj from lib.configobj import ConfigObj
from tornado.ioloop import IOLoop
from daemon import Daemon from daemon import Daemon
signal.signal(signal.SIGINT, sickbeard.sig_handler) signal.signal(signal.SIGINT, sickbeard.sig_handler)
@ -73,331 +74,343 @@ signal.signal(signal.SIGTERM, sickbeard.sig_handler)
throwaway = datetime.datetime.strptime('20110101', '%Y%m%d') throwaway = datetime.datetime.strptime('20110101', '%Y%m%d')
restart = False class SickRage(object):
daemon = None
startPort = None
forceUpdate = None
noLaunch = None
web_options = None
def loadShowsFromDB(): def loadShowsFromDB(self):
""" """
Populates the showList with shows from the database Populates the showList with shows from the database
""" """
logger.log(u"Loading initial show list") logger.log(u"Loading initial show list")
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select("SELECT * FROM tv_shows") sqlResults = myDB.select("SELECT * FROM tv_shows")
sickbeard.showList = [] sickbeard.showList = []
for sqlShow in sqlResults: for sqlShow in sqlResults:
try:
curShow = TVShow(int(sqlShow["indexer"]), int(sqlShow["indexer_id"]))
sickbeard.showList.append(curShow)
except Exception, e:
logger.log(
u"There was an error creating the show in " + sqlShow["location"] + ": " + str(e).decode('utf-8'),
logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG)
def restore(self, srcDir, dstDir):
try: try:
curShow = TVShow(int(sqlShow["indexer"]), int(sqlShow["indexer_id"])) for file in os.listdir(srcDir):
sickbeard.showList.append(curShow) srcFile = os.path.join(srcDir, file)
except Exception, e: dstFile = os.path.join(dstDir, file)
logger.log( bakFile = os.path.join(dstDir, file + '.bak')
u"There was an error creating the show in " + sqlShow["location"] + ": " + str(e).decode('utf-8'), shutil.move(dstFile, bakFile)
logger.ERROR) shutil.move(srcFile, dstFile)
logger.log(traceback.format_exc(), logger.DEBUG)
# TODO: update the existing shows if the showlist has something in it os.rmdir(srcDir)
return True
except:
return False
def restore(srcDir, dstDir): def __init__(self):
try: self.daemon = None
for file in os.listdir(srcDir): self.webserver = None
srcFile = os.path.join(srcDir, file) self.runAsDaemon = False
dstFile = os.path.join(dstDir, file) self.CREATEPID = False
bakFile = os.path.join(dstDir, file + '.bak') self.PIDFILE = None
shutil.move(dstFile, bakFile) self.forceUpdate = False
shutil.move(srcFile, dstFile) self.forcedPort = None
self.noLaunch = False
os.rmdir(srcDir) def start(self):
return True # do some preliminary stuff
except: sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
return False sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
def main(): sickbeard.DATA_DIR = sickbeard.PROG_DIR
""" sickbeard.MY_ARGS = sys.argv[1:]
TV for me sickbeard.SYS_ENCODING = None
"""
global daemon, startPort, forceUpdate, noLaunch, web_options
# 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.DAEMON = False
sickbeard.CREATEPID = False
sickbeard.SYS_ENCODING = None
try:
locale.setlocale(locale.LC_ALL, "")
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"):
reload(sys)
try:
# pylint: disable=E1101
# On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError
sys.setdefaultencoding(sickbeard.SYS_ENCODING)
except:
print 'Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable'
print 'or find another way to force Python to use ' + sickbeard.SYS_ENCODING + ' for string encoding.'
sys.exit(1)
# Need console logging for SickBeard.py and SickBeard-console.exe
consoleLogging = (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:], "qfdp::",
['quiet', 'forceupdate', 'daemon', 'port=', 'pidfile=', 'nolaunch', 'config=',
'datadir=']) # @UnusedVariable
except getopt.GetoptError:
print "Available Options: --quiet, --forceupdate, --port, --daemon, --pidfile, --config, --datadir"
sys.exit()
forceUpdate = False
forcedPort = None
noLaunch = False
for o, a in opts:
# For now we'll just silence the logging
if o in ('-q', '--quiet'):
consoleLogging = False
# Should we update (from indexer) all shows in the DB right away?
if o in ('-f', '--forceupdate'):
forceUpdate = 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',):
noLaunch = True
# Override default/configured port
if o in ('-p', '--port'):
forcedPort = int(a)
# Run as a double forked daemon
if o in ('-d', '--daemon'):
sickbeard.DAEMON = True
# When running as daemon disable consoleLogging and don't start browser
consoleLogging = False
noLaunch = True
if sys.platform == 'win32':
sickbeard.DAEMON = False
# 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
# Write a pidfile if requested
if o in ('--pidfile',):
sickbeard.CREATEPID = True
sickbeard.PIDFILE = str(a)
# If the pidfile already exists, sickbeard may still be running, so exit
if os.path.exists(sickbeard.PIDFILE):
sys.exit("PID file: " + sickbeard.PIDFILE + " already exists. Exiting.")
# The pidfile is only useful in daemon mode, make sure we can write the file properly
if sickbeard.CREATEPID:
if sickbeard.DAEMON:
pid_dir = os.path.dirname(sickbeard.PIDFILE)
if not os.access(pid_dir, os.F_OK):
sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.")
if not os.access(pid_dir, os.W_OK):
sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.")
else:
if consoleLogging:
sys.stdout.write("Not running in daemon mode. PID file creation disabled.\n")
sickbeard.CREATEPID = 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, 0744)
except os.error, e:
raise SystemExit("Unable to create datadir '" + sickbeard.DATA_DIR + "'")
# Make sure we can write to the data dir
if not os.access(sickbeard.DATA_DIR, os.W_OK):
raise SystemExit("Datadir must be writeable '" + 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):
raise SystemExit("Config file '" + sickbeard.CONFIG_FILE + "' must be writeable.")
elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK):
raise SystemExit(
"Config file root dir '" + os.path.dirname(sickbeard.CONFIG_FILE) + "' must be writeable.")
# Check if we need to perform a restore first
restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
if os.path.exists(restoreDir):
if restore(restoreDir, sickbeard.DATA_DIR):
logger.log(u"Restore successful...")
else:
logger.log(u"Restore FAILED!", logger.ERROR)
os.chdir(sickbeard.DATA_DIR)
if consoleLogging:
print "Starting up SickRage " + SICKBEARD_VERSION + " from " + sickbeard.CONFIG_FILE
# Load the config and publish it to the sickbeard package
if not os.path.isfile(sickbeard.CONFIG_FILE):
logger.log(u"Unable to find '" + sickbeard.CONFIG_FILE + "' , all settings will be default!", logger.ERROR)
sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
CUR_DB_VERSION = db.DBConnection().checkDBVersion()
if CUR_DB_VERSION > 0:
if CUR_DB_VERSION < MIN_DB_VERSION:
raise SystemExit("Your database version (" + str(
CUR_DB_VERSION) + ") is too old to migrate from with this version of SickRage (" + str(
MIN_DB_VERSION) + ").\n" + \
"Upgrade using a previous version of SB first, or start with no database file to begin fresh.")
if CUR_DB_VERSION > MAX_DB_VERSION:
raise SystemExit("Your database version (" + str(
CUR_DB_VERSION) + ") has been incremented past what this version of SickRage supports (" + str(
MAX_DB_VERSION) + ").\n" + \
"If you have used other forks of SB, your database may be unusable due to their modifications.")
# Initialize the config and our threads
sickbeard.initialize(consoleLogging=consoleLogging)
if forcedPort:
logger.log(u"Forcing web server to port " + str(forcedPort))
startPort = forcedPort
else:
startPort = sickbeard.WEB_PORT
if sickbeard.WEB_LOG:
log_dir = sickbeard.LOG_DIR
else:
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':
webhost = sickbeard.WEB_HOST
else:
if sickbeard.WEB_IPV6:
webhost = '::'
else:
webhost = '0.0.0.0'
# web server options
web_options = {
'port': int(startPort),
'host': webhost,
'data_root': os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME),
'web_root': sickbeard.WEB_ROOT,
'log_dir': log_dir,
'username': sickbeard.WEB_USERNAME,
'password': sickbeard.WEB_PASSWORD,
'enable_https': sickbeard.ENABLE_HTTPS,
'handle_reverse_proxy': sickbeard.HANDLE_REVERSE_PROXY,
'https_cert': sickbeard.HTTPS_CERT,
'https_key': sickbeard.HTTPS_KEY,
}
# Start SickRage
if daemon and daemon.is_running():
daemon.restart(daemonize=sickbeard.DAEMON)
else:
daemon = SickRage(sickbeard.PIDFILE)
daemon.start(daemonize=sickbeard.DAEMON)
class SickRage(Daemon):
def run(self):
global restart, startPort, forceUpdate, noLaunch, web_options
# Use this PID for everything
sickbeard.PID = os.getpid()
try: try:
webserveInit.initWebServer(web_options) locale.setlocale(locale.LC_ALL, "")
except IOError: sickbeard.SYS_ENCODING = locale.getpreferredencoding()
logger.log(u"Unable to start web server, is something else running on port %d?" % startPort, logger.ERROR) except (locale.Error, IOError):
if sickbeard.LAUNCH_BROWSER and not sickbeard.DAEMON: pass
logger.log(u"Launching browser and exiting", logger.ERROR)
sickbeard.launchBrowser(startPort) # 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"):
reload(sys)
try:
# pylint: disable=E1101
# On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError
sys.setdefaultencoding(sickbeard.SYS_ENCODING)
except:
print 'Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable'
print 'or find another way to force Python to use ' + sickbeard.SYS_ENCODING + ' for string encoding.'
sys.exit(1)
# Need console logging for SickBeard.py and SickBeard-console.exe
self.consoleLogging = (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:], "qfdp::",
['quiet', 'forceupdate', 'daemon', 'port=', 'pidfile=', 'nolaunch', 'config=',
'datadir=']) # @UnusedVariable
except getopt.GetoptError:
print "Available Options: --quiet, --forceupdate, --port, --daemon, --pidfile, --config, --datadir"
sys.exit() sys.exit()
for o, a in opts:
# For now we'll just silence the logging
if o in ('-q', '--quiet'):
self.consoleLogging = False
# Should we update (from indexer) all shows in the DB right away?
if o in ('-f', '--forceupdate'):
self.forceUpdate = 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.noLaunch = True
# Override default/configured port
if o in ('-p', '--port'):
self.forcedPort = int(a)
# Run as a double forked daemon
if o in ('-d', '--daemon'):
self.runAsDaemon = True
# When running as daemon disable consoleLogging and don't start browser
self.consoleLogging = False
self.noLaunch = True
if sys.platform == 'win32':
self.runAsDaemon = False
# 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
# Write a pidfile if requested
if o in ('--pidfile',):
self.CREATEPID = True
self.PIDFILE = str(a)
# If the pidfile already exists, sickbeard may still be running, so exit
if os.path.exists(self.PIDFILE):
sys.exit("PID file: " + self.PIDFILE + " already exists. Exiting.")
# The pidfile is only useful in daemon mode, make sure we can write the file properly
if self.CREATEPID:
if self.runAsDaemon:
pid_dir = os.path.dirname(self.PIDFILE)
if not os.access(pid_dir, os.F_OK):
sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.")
if not os.access(pid_dir, os.W_OK):
sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.")
else:
if self.consoleLogging:
sys.stdout.write("Not running in daemon mode. PID file creation disabled.\n")
self.CREATEPID = 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, 0744)
except os.error, e:
raise SystemExit("Unable to create datadir '" + sickbeard.DATA_DIR + "'")
# Make sure we can write to the data dir
if not os.access(sickbeard.DATA_DIR, os.W_OK):
raise SystemExit("Datadir must be writeable '" + 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):
raise SystemExit("Config file '" + sickbeard.CONFIG_FILE + "' must be writeable.")
elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK):
raise SystemExit(
"Config file root dir '" + os.path.dirname(sickbeard.CONFIG_FILE) + "' must be writeable.")
# Check if we need to perform a restore first
restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
if os.path.exists(restoreDir):
if self.restore(restoreDir, sickbeard.DATA_DIR):
logger.log(u"Restore successful...")
else:
logger.log(u"Restore FAILED!", logger.ERROR)
os.chdir(sickbeard.DATA_DIR)
# Load the config and publish it to the sickbeard package
if not os.path.isfile(sickbeard.CONFIG_FILE):
logger.log(u"Unable to find '" + sickbeard.CONFIG_FILE + "' , all settings will be default!", logger.ERROR)
sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
CUR_DB_VERSION = db.DBConnection().checkDBVersion()
if CUR_DB_VERSION > 0:
if CUR_DB_VERSION < MIN_DB_VERSION:
raise SystemExit("Your database version (" + str(
CUR_DB_VERSION) + ") is too old to migrate from with this version of SickRage (" + str(
MIN_DB_VERSION) + ").\n" + \
"Upgrade using a previous version of SB first, or start with no database file to begin fresh.")
if CUR_DB_VERSION > MAX_DB_VERSION:
raise SystemExit("Your database version (" + str(
CUR_DB_VERSION) + ") has been incremented past what this version of SickRage supports (" + str(
MAX_DB_VERSION) + ").\n" + \
"If you have used other forks of SB, your database may be unusable due to their modifications.")
# Initialize the config and our threads
sickbeard.initialize(consoleLogging=self.consoleLogging)
if self.runAsDaemon:
self.daemon = Daemon(self.PIDFILE or os.path.join(sickbeard.DATA_DIR, 'sickbeard.pid'))
self.daemon.daemonize()
# Get PID
sickbeard.PID = os.getpid()
if self.forcedPort:
logger.log(u"Forcing web server to port " + str(self.forcedPort))
self.startPort = self.forcedPort
else:
self.startPort = 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:
if sickbeard.WEB_IPV6:
self.webhost = '::'
else:
self.webhost = '0.0.0.0'
# web server options
self.web_options = {
'port': int(self.startPort),
'host': self.webhost,
'data_root': os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME),
'web_root': sickbeard.WEB_ROOT,
'log_dir': self.log_dir,
'username': sickbeard.WEB_USERNAME,
'password': sickbeard.WEB_PASSWORD,
'enable_https': sickbeard.ENABLE_HTTPS,
'handle_reverse_proxy': sickbeard.HANDLE_REVERSE_PROXY,
'https_cert': sickbeard.HTTPS_CERT,
'https_key': sickbeard.HTTPS_KEY,
}
# start web server
try:
self.webserver = SRWebServer(self.web_options)
self.webserver.start()
except IOError:
logger.log(u"Unable to start web server, is something else running on port %d?" % self.startPort,
logger.ERROR)
if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon:
logger.log(u"Launching browser and exiting", logger.ERROR)
sickbeard.launchBrowser(self.startPort)
os._exit(1)
if self.consoleLogging:
print "Starting up SickRage " + SICKBEARD_VERSION + " from " + sickbeard.CONFIG_FILE
# Build from the DB to start with # Build from the DB to start with
loadShowsFromDB() self.loadShowsFromDB()
# Fire up all our threads # Fire up all our threads
sickbeard.start() sickbeard.start()
# Launch browser if we're supposed to
if sickbeard.LAUNCH_BROWSER and not noLaunch:
sickbeard.launchBrowser(startPort)
# Start an update if we're supposed to # Start an update if we're supposed to
if forceUpdate or sickbeard.UPDATE_SHOWS_ON_START: if self.forceUpdate or sickbeard.UPDATE_SHOWS_ON_START:
sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable
if sickbeard.LAUNCH_BROWSER and not (noLaunch or sickbeard.DAEMON or restart): if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
sickbeard.launchBrowser(startPort) sickbeard.launchBrowser(self.startPort)
# reset this if sickrage was restarted while(sickbeard.started):
restart = False time.sleep(1)
# start IO loop
IOLoop.current().start()
# close IO loop
IOLoop.current().close(True)
# stop all tasks
sickbeard.halt()
# save all shows to DB
sickbeard.saveAll()
if __name__ == "__main__": if __name__ == "__main__":
if sys.hexversion >= 0x020600F0: if sys.hexversion >= 0x020600F0:
freeze_support() freeze_support()
while(not sickbeard.shutdown): sr = None
main() try:
# init sickrage
sr = SickRage()
logger.log("SickRage is restarting, please stand by ...") # start sickrage
restart = True sr.start()
logger.log("Goodbye ...") # shutdown web server
sr.webserver.shutDown()
sr.webserver.join()
sr.webserver = None
# if run as daemon delete the pidfile
if sr.runAsDaemon:
sr.daemon.delpid()
if not sickbeard.shutdown:
install_type = sickbeard.versionCheckScheduler.action.install_type
popen_list = []
if install_type in ('git', 'source'):
popen_list = [sys.executable, sickbeard.MY_FULLNAME]
elif install_type == 'win':
if hasattr(sys, 'frozen'):
# c:\dir\to\updater.exe 12345 c:\dir\to\sickbeard.exe
popen_list = [os.path.join(sickbeard.PROG_DIR, 'updater.exe'), str(sickbeard.PID), sys.executable]
else:
logger.log(u"Unknown SB launch method, please file a bug report about this", logger.ERROR)
popen_list = [sys.executable, os.path.join(sickbeard.PROG_DIR, 'updater.py'), str(sickbeard.PID), sys.executable,
sickbeard.MY_FULLNAME]
if popen_list:
popen_list += sickbeard.MY_ARGS
if '--nolaunch' not in popen_list:
popen_list += ['--nolaunch']
logger.log(u"Restarting SickRage with " + str(popen_list))
logger.close()
subprocess.Popen(popen_list, cwd=os.getcwd())
# exit process
os._exit(0)
except:
if sr:
logger.log(traceback.format_exc(), logger.ERROR)
else:
print(traceback.format_exc())
sys.exit(1)

View File

@ -1,26 +1,3 @@
'''
***
Modified generic daemon class
***
Author: http://www.jejik.com/articles/2007/02/
a_simple_unix_linux_daemon_in_python/www.boxedice.com
License: http://creativecommons.org/licenses/by-sa/3.0/
Changes: 23rd Jan 2009 (David Mytton <david@boxedice.com>)
- Replaced hard coded '/dev/null in __init__ with os.devnull
- Added OS check to conditionally remove code that doesn't
work on OS X
- Added output to console on completion
- Tidied up formatting
11th Mar 2009 (David Mytton <david@boxedice.com>)
- Fixed problem with daemon exiting on Python 2.4
(before SystemExit was part of the Exception base)
13th Aug 2010 (David Mytton <david@boxedice.com>
- Fixed unhandled exception if PID file is empty
'''
# Core modules # Core modules
import atexit import atexit
import os import os
@ -57,7 +34,7 @@ class Daemon(object):
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
# Exit first parent # Exit first parent
sys.exit(0) os._exit(0)
except OSError, e: except OSError, e:
sys.stderr.write( sys.stderr.write(
"fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) "fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
@ -73,7 +50,7 @@ class Daemon(object):
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
# Exit from second parent # Exit from second parent
sys.exit(0) os._exit(0)
except OSError, e: except OSError, e:
sys.stderr.write( sys.stderr.write(
"fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) "fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
@ -93,11 +70,6 @@ class Daemon(object):
os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno()) os.dup2(se.fileno(), sys.stderr.fileno())
def sigtermhandler(signum, frame):
self.daemon_alive = False
signal.signal(signal.SIGTERM, sigtermhandler)
signal.signal(signal.SIGINT, sigtermhandler)
if self.verbose >= 1: if self.verbose >= 1:
print "Started" print "Started"
@ -110,43 +82,38 @@ class Daemon(object):
def delpid(self): def delpid(self):
os.remove(self.pidfile) os.remove(self.pidfile)
def start(self, daemonize=True, *args, **kwargs): def start(self, *args, **kwargs):
""" """
Start the daemon Start the daemon
""" """
if daemonize: if self.verbose >= 1:
if self.verbose >= 1: print "Starting..."
print "Starting..."
# Check for a pidfile to see if the daemon already runs # Check for a pidfile to see if the daemon already runs
try: try:
pf = file(self.pidfile, 'r') pf = file(self.pidfile, 'r')
pid = int(pf.read().strip()) pid = int(pf.read().strip())
pf.close() pf.close()
except IOError: except IOError:
pid = None pid = None
except SystemExit: except SystemExit:
pid = None pid = None
if pid: if pid:
message = "pidfile %s already exists. Is it already running?\n" message = "pidfile %s already exists. Is it already running?\n"
sys.stderr.write(message % self.pidfile) sys.stderr.write(message % self.pidfile)
sys.exit(1) sys.exit(1)
# Start the daemon
self.daemonize()
# Start the daemon
self.daemonize()
self.run(*args, **kwargs) self.run(*args, **kwargs)
def stop(self, daemonize=True): def stop(self):
""" """
Stop the daemon Stop the daemon
""" """
if not daemonize:
return
if self.verbose >= 1: if self.verbose >= 1:
print "Stopping..." print "Stopping..."
@ -185,12 +152,12 @@ class Daemon(object):
if self.verbose >= 1: if self.verbose >= 1:
print "Stopped" print "Stopped"
def restart(self, daemonize=True): def restart(self):
""" """
Restart the daemon Restart the daemon
""" """
self.stop(daemonize=daemonize) self.stop()
self.start(daemonize=daemonize) self.start()
def get_pid(self): def get_pid(self):
try: try:

View File

@ -174,10 +174,19 @@ class LockBase:
else: else:
self.tname = "" self.tname = ""
dirname = os.path.dirname(self.lock_file) dirname = os.path.dirname(self.lock_file)
# unique name is mostly about the current process, but must
# also contain the path -- otherwise, two adjacent locked
# files conflict (one file gets locked, creating lock-file and
# unique file, the other one gets locked, creating lock-file
# and overwriting the already existing lock-file, then one
# gets unlocked, deleting both lock-file and unique file,
# finally the last lock errors out upon releasing.
self.unique_name = os.path.join(dirname, self.unique_name = os.path.join(dirname,
"%s%s.%s" % (self.hostname, "%s%s.%s%s" % (self.hostname,
self.tname, self.tname,
self.pid)) self.pid,
hash(self.path)))
self.timeout = timeout self.timeout = timeout
def acquire(self, timeout=None): def acquire(self, timeout=None):

View File

@ -28,6 +28,7 @@ from urllib2 import getproxies
from threading import Lock from threading import Lock
# apparently py2exe won't build these unless they're imported somewhere # apparently py2exe won't build these unless they're imported somewhere
import sys
from sickbeard import providers, metadata, config, webserveInit from sickbeard import providers, metadata, config, webserveInit
from sickbeard.providers.generic import GenericProvider from sickbeard.providers.generic import GenericProvider
from providers import ezrss, tvtorrents, btn, newznab, womble, thepiratebay, torrentleech, kat, iptorrents, \ from providers import ezrss, tvtorrents, btn, newznab, womble, thepiratebay, torrentleech, kat, iptorrents, \
@ -1269,7 +1270,7 @@ def halt():
pass pass
__INITIALIZED__ = False __INITIALIZED__ = False
started = False
def remove_pid_file(PIDFILE): def remove_pid_file(PIDFILE):
try: try:
@ -1283,12 +1284,9 @@ def remove_pid_file(PIDFILE):
def sig_handler(signum=None, frame=None): def sig_handler(signum=None, frame=None):
global shutdown
if type(signum) != type(None): if type(signum) != type(None):
logger.log(u"Signal %i caught, saving and exiting..." % int(signum)) logger.log(u"Signal %i caught, saving and exiting..." % int(signum))
shutdown = True saveAndShutdown()
IOLoop.current().stop()
def saveAll(): def saveAll():
global showList global showList
@ -1303,22 +1301,14 @@ def saveAll():
save_config() save_config()
def saveAndShutdown(restart=False): def saveAndShutdown(restart=False):
global shutdown global shutdown, started
# flag restart/shutdown
if not restart: if not restart:
shutdown = True shutdown = True
# stop tornado web server # proceed with shutdown
webserveInit.server.stop() started = False
# stop all tasks
halt()
# save all shows to db
saveAll()
#stop tornado io loop
IOLoop.current().stop()
def invoke_command(to_call, *args, **kwargs): def invoke_command(to_call, *args, **kwargs):
@ -1333,11 +1323,8 @@ def invoke_command(to_call, *args, **kwargs):
def invoke_restart(soft=True): def invoke_restart(soft=True):
invoke_command(restart, soft=soft) invoke_command(restart, soft=soft)
def invoke_shutdown(): def invoke_shutdown():
global shutdown invoke_command(saveAndShutdown, False)
shutdown = True
invoke_command(IOLoop.current().stop)
def restart(soft=True): def restart(soft=True):
if soft: if soft:
@ -1346,7 +1333,7 @@ def restart(soft=True):
logger.log(u"Re-initializing all data") logger.log(u"Re-initializing all data")
initialize() initialize()
else: else:
IOLoop.current().stop() saveAndShutdown(True)
def save_config(): def save_config():

View File

@ -349,6 +349,8 @@ class BTNCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -260,6 +260,8 @@ class HDBitsCache(tvcache.TVCache):
if ci is not None: if ci is not None:
ql.append(ci) ql.append(ci)
time.sleep(.2)
if ql: if ql:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(ql) myDB.mass_action(ql)

View File

@ -382,6 +382,8 @@ class HDTorrentsCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -323,6 +323,8 @@ class IPTorrentsCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -460,6 +460,8 @@ class KATCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -347,6 +347,8 @@ class NewznabCache(tvcache.TVCache):
if ci is not None: if ci is not None:
ql.append(ci) ql.append(ci)
time.sleep(.2)
if ql: if ql:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(ql) myDB.mass_action(ql)

View File

@ -372,6 +372,8 @@ class NextGenCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -345,6 +345,8 @@ class PublicHDCache(tvcache.TVCache):
if ci is not None: if ci is not None:
ql.append(ci) ql.append(ci)
time.sleep(.2)
if ql: if ql:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(ql) myDB.mass_action(ql)

View File

@ -367,6 +367,8 @@ class SCCCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -307,6 +307,8 @@ class SpeedCDCache(tvcache.TVCache):
if ci is not None: if ci is not None:
ql.append(ci) ql.append(ci)
time.sleep(.2)
if ql: if ql:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(ql) myDB.mass_action(ql)

View File

@ -440,6 +440,8 @@ class ThePirateBayCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -331,6 +331,8 @@ class TorrentDayCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -326,6 +326,8 @@ class TorrentLeechCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -15,6 +15,7 @@
# #
# 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 SickRage. If not, see <http://www.gnu.org/licenses/>. # along with SickRage. If not, see <http://www.gnu.org/licenses/>.
import time
import sickbeard import sickbeard
import generic import generic
@ -73,6 +74,8 @@ class WombleCache(tvcache.TVCache):
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -128,6 +128,8 @@ class TVCache():
if ci is not None: if ci is not None:
cl.append(ci) cl.append(ci)
time.sleep(.2)
if cl: if cl:
myDB = self._getDB() myDB = self._getDB()
myDB.mass_action(cl) myDB.mass_action(cl)

View File

@ -1,5 +1,8 @@
import os import os
import traceback import socket
import time
import threading
import sys
import sickbeard import sickbeard
import webserve import webserve
import webapi import webapi
@ -10,9 +13,6 @@ from tornado.web import Application, StaticFileHandler, RedirectHandler, HTTPErr
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
server = None
class MultiStaticFileHandler(StaticFileHandler): class MultiStaticFileHandler(StaticFileHandler):
def initialize(self, paths, default_filename=None): def initialize(self, paths, default_filename=None):
self.paths = paths self.paths = paths
@ -34,85 +34,106 @@ class MultiStaticFileHandler(StaticFileHandler):
# Oops file not found anywhere! # Oops file not found anywhere!
raise HTTPError(404) raise HTTPError(404)
class SRWebServer(threading.Thread):
def __init__(self, options=[], io_loop=None):
threading.Thread.__init__(self)
self.daemon = True
self.alive = True
self.name = "TORNADO"
self.io_loop = io_loop or IOLoop.current()
def initWebServer(options={}): self.options = options
options.setdefault('port', 8081) self.options.setdefault('port', 8081)
options.setdefault('host', '0.0.0.0') self.options.setdefault('host', '0.0.0.0')
options.setdefault('log_dir', None) self.options.setdefault('log_dir', None)
options.setdefault('username', '') self.options.setdefault('username', '')
options.setdefault('password', '') self.options.setdefault('password', '')
options.setdefault('web_root', '/') self.options.setdefault('web_root', '/')
assert isinstance(options['port'], int) assert isinstance(self.options['port'], int)
assert 'data_root' in options assert 'data_root' in self.options
# tornado setup # tornado setup
enable_https = options['enable_https'] self.enable_https = self.options['enable_https']
https_cert = options['https_cert'] self.https_cert = self.options['https_cert']
https_key = options['https_key'] self.https_key = self.options['https_key']
if enable_https: if self.enable_https:
# If either the HTTPS certificate or key do not exist, make some self-signed ones. # If either the HTTPS certificate or key do not exist, make some self-signed ones.
if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)): if not (self.https_cert and os.path.exists(self.https_cert)) or not (self.https_key and os.path.exists(self.https_key)):
if not create_https_certificates(https_cert, https_key): if not create_https_certificates(self.https_cert, self.https_key):
logger.log(u"Unable to create CERT/KEY files, disabling HTTPS") logger.log(u"Unable to create CERT/KEY files, disabling HTTPS")
sickbeard.ENABLE_HTTPS = False
enable_https = False
if not (os.path.exists(self.https_cert) and os.path.exists(self.https_key)):
logger.log(u"Disabled HTTPS because of missing CERT and KEY files", logger.WARNING)
sickbeard.ENABLE_HTTPS = False sickbeard.ENABLE_HTTPS = False
enable_https = False enable_https = False
if not (os.path.exists(https_cert) and os.path.exists(https_key)): # Load the app
logger.log(u"Disabled HTTPS because of missing CERT and KEY files", logger.WARNING) self.app = Application([],
sickbeard.ENABLE_HTTPS = False debug=False,
enable_https = False gzip=True,
xheaders=sickbeard.HANDLE_REVERSE_PROXY,
cookie_secret='61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo='
)
# Load the app # Main Handler
app = Application([], self.app.add_handlers(".*$", [
debug=False, (r"%s" % self.options['web_root'], RedirectHandler, {'url': '%s/home/' % self.options['web_root']}),
gzip=True, (r'%s/api/(.*)(/?)' % self.options['web_root'], webapi.Api),
xheaders=sickbeard.HANDLE_REVERSE_PROXY, (r'%s/(.*)(/?)' % self.options['web_root'], webserve.MainHandler)
cookie_secret='61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=' ])
)
# Main Handler # Static Path Handler
app.add_handlers(".*$", [ self.app.add_handlers(".*$", [
(r"%s" % options['web_root'], RedirectHandler, {'url': '%s/home/' % options['web_root']}), (r'%s/(favicon\.ico)' % self.options['web_root'], MultiStaticFileHandler,
(r'%s/api/(.*)(/?)' % options['web_root'], webapi.Api), {'paths': [os.path.join(self.options['data_root'], 'images/ico/favicon.ico')]}),
(r'%s/(.*)(/?)' % options['web_root'], webserve.MainHandler) (r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'images'), MultiStaticFileHandler,
]) {'paths': [os.path.join(self.options['data_root'], 'images'),
os.path.join(sickbeard.CACHE_DIR, 'images')]}),
(r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'css'), MultiStaticFileHandler,
{'paths': [os.path.join(self.options['data_root'], 'css')]}),
(r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'js'), MultiStaticFileHandler,
{'paths': [os.path.join(self.options['data_root'], 'js')]})
# Static Path Handler ])
app.add_handlers(".*$", [
(r'%s/(favicon\.ico)' % options['web_root'], MultiStaticFileHandler,
{'paths': [os.path.join(options['data_root'], 'images/ico/favicon.ico')]}),
(r'%s/%s/(.*)(/?)' % (options['web_root'], 'images'), MultiStaticFileHandler,
{'paths': [os.path.join(options['data_root'], 'images'),
os.path.join(sickbeard.CACHE_DIR, 'images')]}),
(r'%s/%s/(.*)(/?)' % (options['web_root'], 'css'), MultiStaticFileHandler,
{'paths': [os.path.join(options['data_root'], 'css')]}),
(r'%s/%s/(.*)(/?)' % (options['web_root'], 'js'), MultiStaticFileHandler,
{'paths': [os.path.join(options['data_root'], 'js')]})
]) def run(self):
if self.enable_https:
protocol = "https"
self.server = HTTPServer(self.app, no_keep_alive=True,
ssl_options={"certfile": self.https_cert, "keyfile": self.https_key})
else:
protocol = "http"
self.server = HTTPServer(self.app, no_keep_alive=True)
global server logger.log(u"Starting SickRage on " + protocol + "://" + str(self.options['host']) + ":" + str(
self.options['port']) + "/")
if enable_https: try:
protocol = "https" self.server.listen(self.options['port'], self.options['host'])
server = HTTPServer(app, no_keep_alive=True, except:
ssl_options={"certfile": https_cert, "keyfile": https_key}) etype, evalue, etb = sys.exc_info()
else: logger.log("Could not start webserver on %s. Excpeption: %s, Error: %s" % (self.options['port'], etype, evalue), logger.ERROR)
protocol = "http" return
server = HTTPServer(app, no_keep_alive=True)
logger.log(u"Starting SickRage on " + protocol + "://" + str(options['host']) + ":" + str( try:
options['port']) + "/") self.io_loop.start()
self.io_loop.close(True)
server.listen(options['port'], options['host']) # stop all tasks
sickbeard.halt()
def shutdown(): # save all shows to DB
sickbeard.saveAll()
logger.log('Shutting down tornado IO loop') except ValueError:
try: # Ignore errors like "ValueError: I/O operation on closed kqueue fd". These might be thrown during a reload.
IOLoop.current().stop() pass
except RuntimeError:
pass def shutDown(self):
except: self.alive = False
logger.log('Failed shutting down tornado IO loop: %s' % traceback.format_exc(), logger.ERROR) if self.server:
self.server.stop()
self.io_loop.stop()