Merge remote-tracking branch 'origin/dev'

This commit is contained in:
echel0n 2014-07-02 11:52:55 -07:00
commit ae398031fd
27 changed files with 923 additions and 501 deletions

View file

@ -19,10 +19,11 @@
# 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 functools
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."
@ -60,368 +61,356 @@ 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 daemon import Daemon
from tornado.ioloop import IOLoop
signal.signal(signal.SIGINT, sickbeard.sig_handler) signal.signal(signal.SIGINT, sickbeard.sig_handler)
signal.signal(signal.SIGTERM, sickbeard.sig_handler) signal.signal(signal.SIGTERM, sickbeard.sig_handler)
throwaway = datetime.datetime.strptime('20110101', '%Y%m%d') throwaway = datetime.datetime.strptime('20110101', '%Y%m%d')
def loadShowsFromDB(): class SickRage(object):
"""
Populates the showList with shows from the database
"""
logger.log(u"Loading initial show list") def loadShowsFromDB(self):
"""
Populates the showList with shows from the database
"""
myDB = db.DBConnection() logger.log(u"Loading initial show list")
sqlResults = myDB.select("SELECT * FROM tv_shows")
sickbeard.showList = [] myDB = db.DBConnection()
for sqlShow in sqlResults: sqlResults = myDB.select("SELECT * FROM tv_shows")
try:
curShow = TVShow(int(sqlShow["indexer"]), int(sqlShow["indexer_id"])) sickbeard.showList = []
sickbeard.showList.append(curShow) for sqlShow in sqlResults:
except Exception, e: try:
logger.log( 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'), u"There was an error creating the show in " + sqlShow["location"] + ": " + str(e).decode('utf-8'),
logger.ERROR) logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
# TODO: update the existing shows if the showlist has something in it def restore(self, srcDir, dstDir):
def daemonize():
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError:
print "fork() failed"
sys.exit(1)
os.chdir(sickbeard.PROG_DIR)
os.setsid()
# Make sure I can read my own files and shut out others
prev= os.umask(0)
os.umask(prev and int('077',8))
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError:
print "fork() failed"
sys.exit(1)
# Write pid
if sickbeard.CREATEPID:
pid = str(os.getpid())
logger.log(u"Writing PID: " + pid + " to " + str(sickbeard.PIDFILE))
try: try:
file(sickbeard.PIDFILE, 'w').write("%s\n" % pid) for file in os.listdir(srcDir):
except IOError, e: srcFile = os.path.join(srcDir, file)
logger.log_error_and_exit( dstFile = os.path.join(dstDir, file)
u"Unable to write PID file: " + sickbeard.PIDFILE + " Error: " + str(e.strerror) + " [" + str( bakFile = os.path.join(dstDir, file + '.bak')
e.errno) + "]") shutil.move(dstFile, bakFile)
shutil.move(srcFile, dstFile)
dev_null = file('/dev/null', 'r') os.rmdir(srcDir)
os.dup2(dev_null.fileno(), sys.stdin.fileno()) 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)
sickbeard.DATA_DIR = sickbeard.PROG_DIR
sickbeard.MY_ARGS = sys.argv[1:]
sickbeard.SYS_ENCODING = None
def main():
"""
TV for me
"""
# 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 and not sickbeard.restarted:
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: try:
os.makedirs(sickbeard.DATA_DIR, 0744) locale.setlocale(locale.LC_ALL, "")
except os.error, e: sickbeard.SYS_ENCODING = locale.getpreferredencoding()
raise SystemExit("Unable to create datadir '" + sickbeard.DATA_DIR + "'") except (locale.Error, IOError):
pass
# Make sure we can write to the data dir # For OSes that are poorly configured I'll just randomly force UTF-8
if not os.access(sickbeard.DATA_DIR, os.W_OK): if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
raise SystemExit("Datadir must be writeable '" + sickbeard.DATA_DIR + "'") sickbeard.SYS_ENCODING = 'UTF-8'
# Make sure we can write to the config file if not hasattr(sys, "setdefaultencoding"):
if not os.access(sickbeard.CONFIG_FILE, os.W_OK): reload(sys)
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 try:
restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore') # pylint: disable=E1101
if os.path.exists(restoreDir): # On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError
if restore(restoreDir, sickbeard.DATA_DIR): sys.setdefaultencoding(sickbeard.SYS_ENCODING)
logger.log(u"Restore successful...") 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()
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: else:
logger.log(u"Restore FAILED!", logger.ERROR) self.startPort = sickbeard.WEB_PORT
os.chdir(sickbeard.DATA_DIR) if sickbeard.WEB_LOG:
self.log_dir = sickbeard.LOG_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 sickbeard.DAEMON:
daemonize()
# Use this PID for everything
sickbeard.PID = os.getpid()
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: else:
webhost = '0.0.0.0' self.log_dir = None
options = { # sickbeard.WEB_HOST is available as a configuration value in various
'port': int(startPort), # places but is not configurable. It is supported here for historic reasons.
'host': webhost, if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0':
'data_root': os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), self.webhost = sickbeard.WEB_HOST
'web_root': sickbeard.WEB_ROOT, else:
'log_dir': log_dir, if sickbeard.WEB_IPV6:
'username': sickbeard.WEB_USERNAME, self.webhost = '::'
'password': sickbeard.WEB_PASSWORD, else:
'enable_https': sickbeard.ENABLE_HTTPS, self.webhost = '0.0.0.0'
'handle_reverse_proxy': sickbeard.HANDLE_REVERSE_PROXY,
'https_cert': sickbeard.HTTPS_CERT, # web server options
'https_key': sickbeard.HTTPS_KEY, 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,
} }
# init tornado # start web server
try: try:
webserveInit.initWebServer(options) self.webserver = SRWebServer(self.web_options)
except IOError: self.webserver.start()
logger.log(u"Unable to start web server, is something else running on port %d?" % startPort, logger.ERROR) except IOError:
if sickbeard.LAUNCH_BROWSER and not sickbeard.DAEMON: logger.log(u"Unable to start web server, is something else running on port %d?" % self.startPort,
logger.log(u"Launching browser and exiting", logger.ERROR) logger.ERROR)
sickbeard.launchBrowser(startPort) if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon:
sys.exit() logger.log(u"Launching browser and exiting", logger.ERROR)
sickbeard.launchBrowser(self.startPort)
os._exit(1)
# Build from the DB to start with if self.consoleLogging:
loadShowsFromDB() print "Starting up SickRage " + SICKBEARD_VERSION + " from " + sickbeard.CONFIG_FILE
# Fire up all our threads # Build from the DB to start with
sickbeard.start() self.loadShowsFromDB()
# Launch browser if we're supposed to # Fire up all our threads
if sickbeard.LAUNCH_BROWSER and not noLaunch: sickbeard.start()
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 we restarted then unset the restarted flag if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
if sickbeard.restarted: sickbeard.launchBrowser(self.startPort)
sickbeard.restarted = False
# IOLoop while(sickbeard.started):
io_loop = IOLoop.current() time.sleep(1)
# Open browser window
if sickbeard.LAUNCH_BROWSER and not (noLaunch or sickbeard.DAEMON or sickbeard.restarted):
io_loop.add_timeout(datetime.timedelta(seconds=5), functools.partial(sickbeard.launchBrowser, startPort))
# Start web server
io_loop.start()
# Save and restart/shutdown
sickbeard.saveAndShutdown()
if __name__ == "__main__": if __name__ == "__main__":
if sys.hexversion >= 0x020600F0: if sys.hexversion >= 0x020600F0:
freeze_support() freeze_support()
while(True): sr = None
main() try:
# init sickrage
sr = SickRage()
# check if restart was requested # start sickrage
if not sickbeard.restarted: sr.start()
if sickbeard.CREATEPID:
logger.log(u"Removing pidfile " + str(sickbeard.PIDFILE))
sickbeard.remove_pid_file(sickbeard.PIDFILE)
break
# restart # shutdown web server
logger.log("Restarting SickRage, please stand by...") 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)

92
gui/slick/css/trakt.css Normal file
View file

@ -0,0 +1,92 @@
.traktShowDiv {
clear: both;
border-left: 1px solid #CCCCCC;
border-right: 1px solid #CCCCCC;
border-bottom: 1px solid #CCCCCC;
margin: auto;
padding: 0px;
text-align: left;
width: 750px;
}
.traktShowDiv a, .traktShowDiv a:link, .traktShowDiv a:visited, .traktShowDiv a:hover {
text-decoration: none;
background: none;
}
.traktShowTitle a {
color: #000000;
float: left;
padding-top: 3px;
line-height: 1.2em;
font-size: 1.1em;
text-shadow: -1px -1px 0 #FFF);
}
.traktShowTitleIcons {
float: right;
padding: 3px 5px;
}
.traktShowDiv .title {
font-weight: 900;
color: #333;
}
.imgWrapper {
background: url("../images/loading.gif") no-repeat scroll center center #FFFFFF;
border: 3px solid #FFFFFF;
box-shadow: 1px 1px 2px 0 #555555;
float: left;
height: 50px;
overflow: hidden;
text-indent: -3000px;
width: 50px;
}
.imgWrapper .traktPosterThumb {
float: left;
min-height: 100%;
min-width: 100%;
width: 50px;
height: auto;
position: relative;
border: none;
vertical-align: middle;
}
.traktPosterThumb {
-ms-interpolation-mode: bicubic; /* make scaling look nicer for ie */
vertical-align: top;
height: auto;
width: 160px;
border-top: 1px solid #ccc;
border-right: 1px solid #ccc;
}
.traktShowDiv th {
color: #000;
letter-spacing: 1px;
text-align: left;
background-color: #333333;
}
.traktShowDiv th.nobg {
background: #efefef;
border-top: 1px solid #666;
text-align: center;
}
.traktShowDiv td {
border-top: 1px solid #d2ebe8;
background: #fff;
padding: 5px 10px 5px 10px;
color: #000;
}
.traktShowDiv td.trakts_show {
width: 100%;
height: 90%;
border-top: 1px solid #ccc;
vertical-align: top;
background: #F5FAFA;
color: #000;
}

View file

@ -26,15 +26,23 @@
<p>For shows that you haven't downloaded yet, this option finds a show on theTVDB.com and TVRage.com, creates a directory for its episodes, and adds it to SickRage.</p> <p>For shows that you haven't downloaded yet, this option finds a show on theTVDB.com and TVRage.com, creates a directory for its episodes, and adds it to SickRage.</p>
</div> </div>
</a> </a>
<br/><br/>
<a href="$sbRoot/home/addShows/trendingShows/" id="btnNewShow" class="btn btn-large">
<div class="button"><img src="$sbRoot/images/add-new32.png" height="32" width="32" alt="Add Trending Shows"/></div>
<div class="buttontext">
<h2>Add Trending Show</h2>
<p>For shows that you haven't downloaded yet, this option lets you choose from a list of current trending shows with ratings to add, creates a directory for its episodes, and adds it to SickRage.</p>
</div>
</a>
<br/><br/> <br/><br/>
#if $sickbeard.TRAKT_USE_RECOMMENDED: #if $sickbeard.TRAKT_USE_RECOMMENDED:
<a href="$sbRoot/home/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large"> <a href="$sbRoot/home/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large">
<div class="button"><img src="$sbRoot/images/add-new32.png" height="32" width="32" alt="Add Recommended Shows"/></div> <div class="button"><img src="$sbRoot/images/add-new32.png" height="32" width="32" alt="Add Recommended Shows"/></div>
<div class="buttontext"> <div class="buttontext">
<h2>Add Recommended Shows</h2> <h2>Add Recommended Show</h2>
<p>For shows that you haven't downloaded yet, this option recommends shows to add based on your Trakt.tv watch list, creates a directory for its episodes, and adds it to SickRage. *** Trakt.tv must be enabled ***</p> <p>For shows that you haven't downloaded yet, this option recommends shows to add based on your Trakt.tv show library, creates a directory for its episodes, and adds it to SickRage. *** Trakt.tv must be enabled ***</p>
</div> </div>
</a> </a>
<br/><br/> <br/><br/>

View file

@ -0,0 +1,58 @@
#import sickbeard
#import datetime
#import re
#from sickbeard.common import *
#from sickbeard import sbdatetime
#set global $title="Trending Shows"
#set global $header="Trending Shows"
#set global $sbPath=".."
#set global $topmenu="comingEpisodes"
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/trakt.css?$sbPID" />
#if $varExists('header')
<h1 class="header">$header</h1>
#else
<h1 class="title">$title</h1>
#end if
<div class="traktShowDiv">
<table width="100%" cellspacing="1" border="0" cellpadding="0">
#for $i, $cur_show in $enumerate($trending_shows):
<div id="listing_${cur_show["tvdb_id"]}">
#if not $i%4
<tr>
#end if
<th style="background-color: #efefef;" valign="top">
<td class="trakt_show">
<a href="${cur_show["url"]}"><img alt="" class="traktPosterThumb" src="${cur_show["images"]["poster"]}" /></a>
<div class="clearfix">
<h2>$cur_show["ratings"]["percentage"]% <img src="$sbRoot/images/like.png"></h2>
<i>$cur_show["ratings"]["votes"] votes</i>
<div class="traktShowTitleIcons">
<a href="$sbRoot/home/addTraktShow?indexer_id=${cur_show["tvdb_id"]}&amp;showName=${cur_show["title"]}"><img alt="[add show]" height="16" width="16" src="$sbRoot/images/plus.png"></a>
</div>
</div>
</td>
</th>
</div>
#end for
</table>
</div>
<script type="text/javascript" charset="utf-8">
<!--
window.setInterval( "location.reload(true)", 600000); // Refresh every 10 minutes
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_bottom.tmpl")

View file

@ -1,30 +1,55 @@
if (sbHandleReverseProxy != "False" && sbHandleReverseProxy != 0) if (sbHttpsEnabled != "False" && sbHttpsEnabled != 0) {
// Don't add the port to the url if using reverse proxy var sb_base_url = 'https://' + sbHost + ':' + sbHttpPort + sbRoot;
if (sbHttpsEnabled != "False" && sbHttpsEnabled != 0) } else {
var sb_base_url = 'https://'+sbHost+sbRoot; var sb_base_url = 'http://' + sbHost + ':' + sbHttpPort + sbRoot;
else }
var sb_base_url = 'http://'+sbHost+sbRoot;
else
if (sbHttpsEnabled != "False" && sbHttpsEnabled != 0)
var sb_base_url = 'https://'+sbHost+':'+sbHttpPort+sbRoot;
else
var sb_base_url = 'http://'+sbHost+':'+sbHttpPort+sbRoot;
var base_url = window.location.protocol+'//'+window.location.host+sbRoot; var base_url = window.location.protocol + '//' + window.location.host + sbRoot;
var is_alive_url = sbRoot+'/home/is_alive'; var is_alive_url = sbRoot + '/home/is_alive/';
var timeout_id; var timeout_id;
var restarted = ''; var current_pid = '';
var num_restart_waits = 0; var num_restart_waits = 0;
function restartHandler() { function is_alive() {
timeout_id = 0;
$.get(is_alive_url, function(data) {
// if it's still initalizing then just wait and try again
if (data.msg == 'nope') {
$('#shut_down_loading').hide();
$('#shut_down_success').show();
$('#restart_message').show();
setTimeout('is_alive()', 1000);
} else {
// if this is before we've even shut down then just try again later
if (current_pid == '' || data.msg == current_pid) {
current_pid = data.msg;
setTimeout(is_alive, 1000);
// if we're ready to go then redirect to new url
} else {
$('#restart_loading').hide();
$('#restart_success').show();
$('#refresh_message').show();
window.location = sb_base_url + '/home/';
}
}
}, 'jsonp');
}
$(document).ready(function() {
is_alive();
$('#shut_down_message').ajaxError(function(e, jqxhr, settings, exception) {
num_restart_waits += 1; num_restart_waits += 1;
$('#shut_down_loading').hide(); $('#shut_down_loading').hide();
$('#shut_down_success').show(); $('#shut_down_success').show();
$('#restart_message').show(); $('#restart_message').show();
is_alive_url = sb_base_url+'/home/is_alive'; is_alive_url = sb_base_url + '/home/is_alive/';
// if https is enabled or you are currently on https and the port or protocol changed just wait 5 seconds then redirect. // if https is enabled or you are currently on https and the port or protocol changed just wait 5 seconds then redirect.
// This is because the ajax will fail if the cert is untrusted or the the http ajax requst from https will fail because of mixed content error. // This is because the ajax will fail if the cert is untrusted or the the http ajax requst from https will fail because of mixed content error.
if ((sbHttpsEnabled != "False" && sbHttpsEnabled != 0) || window.location.protocol == "https:") { if ((sbHttpsEnabled != "False" && sbHttpsEnabled != 0) || window.location.protocol == "https:") {
if (base_url != sb_base_url) { if (base_url != sb_base_url) {
@ -34,16 +59,8 @@ function restartHandler() {
$('#restart_success').show(); $('#restart_success').show();
$('#refresh_message').show(); $('#refresh_message').show();
}, 3000); }, 3000);
setTimeout("window.location = sb_base_url+'/home/'", 5000); setTimeout("window.location = sb_base_url + '/home/'", 5000);
} }
} else {
timeout_id = 1;
setTimeout(function(){
$('#restart_loading').hide();
$('#restart_success').show();
$('#refresh_message').show();
}, 3000);
setTimeout("window.location = sb_base_url+'/home/'", 5000);
} }
// if it is taking forever just give up // if it is taking forever just give up
@ -54,34 +71,9 @@ function restartHandler() {
return; return;
} }
if (timeout_id == 0) if (timeout_id == 0) {
timeout_id = setTimeout('is_alive()', 1000); timeout_id = setTimeout('is_alive()', 1000);
}
function is_alive() {
timeout_id = 0;
$.get(is_alive_url, function(data) {
// if it's still initalizing then just wait and try again
if (data.msg == 'nope') {
$('#shut_down_loading').hide();
$('#shut_down_success').show();
$('#restart_message').show();
setTimeout('is_alive()', 1000);
} else if (data.restarted == 'True') {
restartHandler();
} else {
// if this is before we've even shut down then just try again later
if (restarted == '' || data.restarted == restarted) {
restarted = data.restarted;
setTimeout(is_alive, 1000);
}
} }
}, 'jsonp'); });
}
$(document).ready(function() });
{
is_alive();
});

183
lib/daemon.py Normal file
View file

@ -0,0 +1,183 @@
# Core modules
import atexit
import os
import sys
import time
import signal
class Daemon(object):
"""
A generic daemon class.
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin=os.devnull,
stdout=os.devnull, stderr=os.devnull,
home_dir='.', umask=022, verbose=1):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
self.home_dir = home_dir
self.verbose = verbose
self.umask = umask
self.daemon_alive = True
def daemonize(self):
"""
Do the UNIX double-fork magic, see Stevens' "Advanced
Programming in the UNIX Environment" for details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
"""
try:
pid = os.fork()
if pid > 0:
# Exit first parent
os._exit(0)
except OSError, e:
sys.stderr.write(
"fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
# Decouple from parent environment
os.chdir(self.home_dir)
os.setsid()
os.umask(self.umask)
# Do second fork
try:
pid = os.fork()
if pid > 0:
# Exit from second parent
os._exit(0)
except OSError, e:
sys.stderr.write(
"fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
if sys.platform != 'darwin': # This block breaks on OS X
# Redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
if self.stderr:
se = file(self.stderr, 'a+', 0)
else:
se = so
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
if self.verbose >= 1:
print "Started"
# Write pidfile
atexit.register(
self.delpid) # Make sure pid file is removed if we quit
pid = str(os.getpid())
file(self.pidfile, 'w+').write("%s\n" % pid)
def delpid(self):
os.remove(self.pidfile)
def start(self, *args, **kwargs):
"""
Start the daemon
"""
if self.verbose >= 1:
print "Starting..."
# Check for a pidfile to see if the daemon already runs
try:
pf = file(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
except SystemExit:
pid = None
if pid:
message = "pidfile %s already exists. Is it already running?\n"
sys.stderr.write(message % self.pidfile)
sys.exit(1)
# Start the daemon
self.daemonize()
self.run(*args, **kwargs)
def stop(self):
"""
Stop the daemon
"""
if self.verbose >= 1:
print "Stopping..."
# Get the pid from the pidfile
pid = self.get_pid()
if not pid:
message = "pidfile %s does not exist. Not running?\n"
sys.stderr.write(message % self.pidfile)
# Just to be sure. A ValueError might occur if the PID file is
# empty but does actually exist
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
return # Not an error in a restart
# Try killing the daemon process
try:
i = 0
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
i = i + 1
if i % 10 == 0:
os.kill(pid, signal.SIGHUP)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print str(err)
sys.exit(1)
if self.verbose >= 1:
print "Stopped"
def restart(self):
"""
Restart the daemon
"""
self.stop()
self.start()
def get_pid(self):
try:
pf = file(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
except SystemExit:
pid = None
return pid
def is_running(self):
pid = self.get_pid()
print(pid)
return pid and os.path.exists('/proc/%d' % pid)
def run(self):
"""
You should override this method when you subclass Daemon.
It will be called after the process has been
daemonized by start() or restart().
"""

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

@ -7,7 +7,7 @@ try:
except ImportError: except ImportError:
from lib import simplejson as json from lib import simplejson as json
def TraktCall(method, api, username, password, data = {}): def TraktCall(method, api, username=None, password=None, data={}):
""" """
A generic method for communicating with trakt. Uses the method and data provided along A generic method for communicating with trakt. Uses the method and data provided along
with the auth info to send the command. with the auth info to send the command.
@ -26,19 +26,16 @@ def TraktCall(method, api, username, password, data = {}):
return None return None
# if the username isn't given then it failed # if the username isn't given then it failed
if not username: if username and password:
return None password = sha1(password).hexdigest()
data["username"] = username
password = sha1(password).hexdigest() data["password"] = password
# replace the API string with what we found # replace the API string with what we found
method = method.replace("%API%", api) method = method.replace("%API%", api)
data["username"] = username
data["password"] = password
# take the URL params and make a json object out of them # take the URL params and make a json object out of them
encoded_data = json.dumps(data); encoded_data = json.dumps(data)
# request the URL from trakt and parse the result as json # request the URL from trakt and parse the result as json
try: try:

View file

@ -19,7 +19,6 @@
from __future__ import with_statement from __future__ import with_statement
import webbrowser import webbrowser
import time
import datetime import datetime
import socket import socket
import os import os
@ -29,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, \
@ -104,7 +104,7 @@ CUR_COMMIT_HASH = None
INIT_LOCK = Lock() INIT_LOCK = Lock()
started = False started = False
restarted = False shutdown = False
ACTUAL_LOG_DIR = None ACTUAL_LOG_DIR = None
LOG_DIR = None LOG_DIR = None
@ -432,7 +432,7 @@ IGNORE_WORDS = "german,french,core2hd,dutch,swedish,reenc,MrLss"
CALENDAR_UNPROTECTED = False CALENDAR_UNPROTECTED = False
TMDB_API_KEY = 'edc5f123313769de83a71e157758030b' TMDB_API_KEY = 'edc5f123313769de83a71e157758030b'
TRAKT_API_KEY = 'abd806c54516240c76e4ebc9c5ccf394'
__INITIALIZED__ = False __INITIALIZED__ = False
def initialize(consoleLogging=True): def initialize(consoleLogging=True):
@ -1270,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:
@ -1286,7 +1286,7 @@ def remove_pid_file(PIDFILE):
def sig_handler(signum=None, frame=None): def sig_handler(signum=None, frame=None):
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))
webserveInit.shutdown() saveAndShutdown()
def saveAll(): def saveAll():
global showList global showList
@ -1300,9 +1300,15 @@ def saveAll():
logger.log(u"Saving config file to disk") logger.log(u"Saving config file to disk")
save_config() save_config()
def saveAndShutdown(): def saveAndShutdown(restart=False):
halt() global shutdown, started
saveAll()
# flag restart/shutdown
if not restart:
shutdown = True
# proceed with shutdown
started = False
def invoke_command(to_call, *args, **kwargs): def invoke_command(to_call, *args, **kwargs):
@ -1317,23 +1323,17 @@ 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():
invoke_command(webserveInit.shutdown) invoke_command(saveAndShutdown, False)
def restart(soft=True): def restart(soft=True):
global restarted
if soft: if soft:
halt() halt()
saveAll() saveAll()
logger.log(u"Re-initializing all data") logger.log(u"Re-initializing all data")
initialize() initialize()
else: else:
restarted=True saveAndShutdown(True)
time.sleep(5)
webserveInit.shutdown()
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

@ -147,9 +147,9 @@ class TraktChecker():
""" """
Adds a new show with the default settings Adds a new show with the default settings
""" """
showObj = helpers.findCertainShow(sickbeard.showList, int(indexerid)) if helpers.findCertainShow(sickbeard.showList, int(indexerid)):
if showObj != None:
return return
logger.log(u"Adding show " + str(indexerid)) logger.log(u"Adding show " + str(indexerid))
root_dirs = sickbeard.ROOT_DIRS.split('|') root_dirs = sickbeard.ROOT_DIRS.split('|')
location = root_dirs[int(root_dirs[0]) + 1] location = root_dirs[int(root_dirs[0]) + 1]
@ -161,6 +161,7 @@ class TraktChecker():
return return
else: else:
helpers.chmodAsParent(showPath) helpers.chmodAsParent(showPath)
sickbeard.showQueueScheduler.action.addShow(1, int(indexerid), showPath, status, sickbeard.showQueueScheduler.action.addShow(1, int(indexerid), showPath, status,
int(sickbeard.QUALITY_DEFAULT), int(sickbeard.QUALITY_DEFAULT),
int(sickbeard.FLATTEN_FOLDERS_DEFAULT)) int(sickbeard.FLATTEN_FOLDERS_DEFAULT))

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

@ -48,7 +48,7 @@ from sickbeard import subtitles
from sickbeard import network_timezones from sickbeard import network_timezones
from sickbeard.providers import newznab, rsstorrent from sickbeard.providers import newznab, rsstorrent
from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets, SKIPPED
from sickbeard.common import SNATCHED, UNAIRED, IGNORED, ARCHIVED, WANTED, FAILED from sickbeard.common import SNATCHED, UNAIRED, IGNORED, ARCHIVED, WANTED, FAILED
from sickbeard.common import SD, HD720p, HD1080p from sickbeard.common import SD, HD720p, HD1080p
from sickbeard.exceptions import ex from sickbeard.exceptions import ex
@ -82,6 +82,7 @@ from lib import adba
from Cheetah.Template import Template from Cheetah.Template import Template
from tornado.web import RequestHandler, HTTPError from tornado.web import RequestHandler, HTTPError
def authenticated(handler_class): def authenticated(handler_class):
def wrap_execute(handler_execute): def wrap_execute(handler_execute):
def basicauth(handler, transforms, *args, **kwargs): def basicauth(handler, transforms, *args, **kwargs):
@ -125,6 +126,7 @@ def authenticated(handler_class):
handler_class._execute = wrap_execute(handler_class._execute) handler_class._execute = wrap_execute(handler_class._execute)
return handler_class return handler_class
class HTTPRedirect(Exception): class HTTPRedirect(Exception):
"""Exception raised when the request should be redirected.""" """Exception raised when the request should be redirected."""
@ -138,9 +140,11 @@ class HTTPRedirect(Exception):
"""Use this exception as a request.handler (raise self).""" """Use this exception as a request.handler (raise self)."""
raise self raise self
def redirect(url, permanent=False, status=None): def redirect(url, permanent=False, status=None):
raise HTTPRedirect(url, permanent, status) raise HTTPRedirect(url, permanent, status)
@authenticated @authenticated
class MainHandler(RequestHandler): class MainHandler(RequestHandler):
def http_error_401_handler(self): def http_error_401_handler(self):
@ -216,7 +220,7 @@ class MainHandler(RequestHandler):
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
try: try:
self.finish(self._dispatch()) self.finish(self._dispatch())
except HTTPRedirect,inst: except HTTPRedirect, inst:
self.redirect(inst.url, inst.permanent, inst.status) self.redirect(inst.url, inst.permanent, inst.status)
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
@ -462,6 +466,7 @@ class MainHandler(RequestHandler):
browser = WebFileBrowser browser = WebFileBrowser
class PageTemplate(Template): class PageTemplate(Template):
def __init__(self, headers, *args, **KWs): def __init__(self, headers, *args, **KWs):
KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/", KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/",
@ -499,7 +504,7 @@ class PageTemplate(Template):
{'title': 'Manage', 'key': 'manage'}, {'title': 'Manage', 'key': 'manage'},
{'title': 'Config', 'key': 'config'}, {'title': 'Config', 'key': 'config'},
{'title': logPageTitle, 'key': 'errorlogs'}, {'title': logPageTitle, 'key': 'errorlogs'},
] ]
class IndexerWebUI(MainHandler): class IndexerWebUI(MainHandler):
@ -518,6 +523,7 @@ class IndexerWebUI(MainHandler):
def _munge(string): def _munge(string):
return unicode(string).encode('utf-8', 'xmlcharrefreplace') return unicode(string).encode('utf-8', 'xmlcharrefreplace')
def _getEpisode(show, season=None, episode=None, absolute=None): def _getEpisode(show, season=None, episode=None, absolute=None):
if show is None: if show is None:
return "Invalid show parameters" return "Invalid show parameters"
@ -2643,7 +2649,7 @@ class NewHomeAddShows(MainHandler):
'display_dir': '<b>' + ek.ek(os.path.dirname, cur_path) + os.sep + '</b>' + ek.ek( 'display_dir': '<b>' + ek.ek(os.path.dirname, cur_path) + os.sep + '</b>' + ek.ek(
os.path.basename, os.path.basename,
cur_path), cur_path),
} }
# see if the folder is in XBMC already # see if the folder is in XBMC already
dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path]) dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path])
@ -2745,10 +2751,10 @@ class NewHomeAddShows(MainHandler):
logger.log(u"Could not connect to trakt service, aborting recommended list update", logger.ERROR) logger.log(u"Could not connect to trakt service, aborting recommended list update", logger.ERROR)
return return
map(final_results.append, ([int(show['tvdb_id']), show['url'], show['title'], show['overview'], map(final_results.append,
datetime.date.fromtimestamp(show['first_aired']).strftime('%Y%m%d')] for show in ([int(show['tvdb_id']), show['url'], show['title'], show['overview'],
recommendedlist if datetime.date.fromtimestamp(int(show['first_aired']) / 1000.0).strftime('%Y%m%d')] for show in
not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id'])))) recommendedlist if not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id']))))
return json.dumps({'results': final_results}) return json.dumps({'results': final_results})
@ -2764,10 +2770,22 @@ class NewHomeAddShows(MainHandler):
show_name = whichSeries.split('|')[2] show_name = whichSeries.split('|')[2]
return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]), return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]),
indexerLang, rootDir, indexerLang, rootDir,
defaultStatus, defaultStatus,
anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows, anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows,
skipShow, providedIndexer, anime, scene) skipShow, providedIndexer, anime, scene)
def trendingShows(self, *args, **kwargs):
"""
Display the new show page which collects a tvdb id, folder, and extra options and
posts them to addNewShow
"""
t = PageTemplate(headers=self.request.headers, file="home_trendingShows.tmpl")
t.submenu = HomeMenu()
t.trending_shows = TraktCall("shows/trending.json/%API%/", sickbeard.TRAKT_API_KEY)
return _munge(t)
def existingShows(self, *args, **kwargs): def existingShows(self, *args, **kwargs):
""" """
@ -2778,6 +2796,33 @@ class NewHomeAddShows(MainHandler):
return _munge(t) return _munge(t)
def addTraktShow(self, indexer_id, showName):
if helpers.findCertainShow(sickbeard.showList, int(indexer_id)):
return
root_dirs = sickbeard.ROOT_DIRS.split('|')
location = root_dirs[int(root_dirs[0]) + 1]
show_dir = ek.ek(os.path.join, location, helpers.sanitizeFileName(showName))
dir_exists = helpers.makeDir(show_dir)
if not dir_exists:
logger.log(u"Unable to create the folder " + show_dir + ", can't add the show", logger.ERROR)
return
else:
helpers.chmodAsParent(show_dir)
sickbeard.showQueueScheduler.action.addShow(1, int(indexer_id), show_dir,
default_status=sickbeard.STATUS_DEFAULT,
quality=sickbeard.QUALITY_DEFAULT,
flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT,
subtitles=sickbeard.SUBTITLES_DEFAULT,
anime=sickbeard.ANIME_DEFAULT,
scene=sickbeard.SCENE_DEFAULT)
ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
# done adding show
redirect('/home/')
def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None, def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None, anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
@ -3054,9 +3099,9 @@ class Home(MainHandler):
if sickbeard.started: if sickbeard.started:
return callback + '(' + json.dumps( return callback + '(' + json.dumps(
{"msg": str(sickbeard.PID), "restarted": str(sickbeard.restarted)}) + ');' {"msg": str(sickbeard.PID)}) + ');'
else: else:
return callback + '(' + json.dumps({"msg": "nope", "restarted": str(sickbeard.restarted)}) + ');' return callback + '(' + json.dumps({"msg": "nope"}) + ');'
def index(self, *args, **kwargs): def index(self, *args, **kwargs):
@ -3379,7 +3424,6 @@ class Home(MainHandler):
return _munge(t) return _munge(t)
def update(self, pid=None): def update(self, pid=None):
if str(pid) != str(sickbeard.PID): if str(pid) != str(sickbeard.PID):
@ -3394,7 +3438,7 @@ class Home(MainHandler):
return _munge(t) return _munge(t)
else: else:
return self._genericMessage("Update Failed", return self._genericMessage("Update Failed",
"Update wasn't successful, not restarting. Check your log for more information.") "Update wasn't successful, not restarting. Check your log for more information.")
def displayShow(self, show=None): def displayShow(self, show=None):
@ -3999,7 +4043,6 @@ class Home(MainHandler):
myDB = db.DBConnection() myDB = db.DBConnection()
myDB.mass_action(sql_l) myDB.mass_action(sql_l)
if int(status) == WANTED: if int(status) == WANTED:
msg = "Backlog was automatically started for the following seasons of <b>" + showObj.name + "</b>:<br />" msg = "Backlog was automatically started for the following seasons of <b>" + showObj.name + "</b>:<br />"
for season in segment: for season in segment:
@ -4294,9 +4337,9 @@ class Home(MainHandler):
return json.dumps({'result': 'failure'}) return json.dumps({'result': 'failure'})
class UI(MainHandler): class UI(MainHandler):
def add_message(self): def add_message(self):
ui.notifications.message('Test 1', 'This is test number 1') ui.notifications.message('Test 1', 'This is test number 1')
ui.notifications.error('Test 2', 'This is test number 2') ui.notifications.error('Test 2', 'This is test number 2')
@ -4307,8 +4350,8 @@ class UI(MainHandler):
cur_notification_num = 1 cur_notification_num = 1
for cur_notification in ui.notifications.get_notifications(self.request.remote_ip): for cur_notification in ui.notifications.get_notifications(self.request.remote_ip):
messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title, messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title,
'message': cur_notification.message, 'message': cur_notification.message,
'type': cur_notification.type} 'type': cur_notification.type}
cur_notification_num += 1 cur_notification_num += 1
return json.dumps(messages) return json.dumps(messages)

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,87 +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)
if not sickbeard.restarted: # stop all tasks
server.listen(options['port'], options['host']) sickbeard.halt()
def shutdown(): # save all shows to DB
global server 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()