diff --git a/.gitignore b/.gitignore index 78c1cef926..8841fd4166 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.pyc cache/* -cache.db +cache.db* config.ini Logs/* sickbeard.db* diff --git a/SickBeard.py b/SickBeard.py index ba46efc0a3..6aab1d93ae 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -1,165 +1,172 @@ -#!/usr/bin/env python -# Author: Nic Wolfe -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of Sick Beard. -# -# Sick Beard is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Sick Beard is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Sick Beard. If not, see . - -import sys - -# we only need this for compiling an EXE and I will just always do that on 2.6+ -if sys.hexversion >= 0x020600F0: - from multiprocessing import Process, freeze_support - -import locale -import os -import os.path -import threading -import time -import signal -import sqlite3 -import traceback -import getopt - -import sickbeard - -from sickbeard import db -from sickbeard.tv import TVShow -from sickbeard import logger -from sickbeard.common import * -from sickbeard.version import SICKBEARD_VERSION - -from sickbeard.webserveInit import initWebServer - -from lib.configobj import ConfigObj - -signal.signal(signal.SIGINT, sickbeard.sig_handler) -signal.signal(signal.SIGTERM, sickbeard.sig_handler) - -def loadShowsFromDB(): - - myDB = db.DBConnection() - sqlResults = myDB.select("SELECT * FROM tv_shows") - - for sqlShow in sqlResults: - try: - curShow = TVShow(int(sqlShow["tvdb_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) - - #TODO: make it update the existing shows if the showlist has something in it - -def daemonize(): - # Make a non-session-leader child process - try: - pid = os.fork() - if pid != 0: - sys.exit(0) - except OSError, e: - raise RuntimeError("1st fork failed: %s [%d]" % - (e.strerror, e.errno)) - - 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)) - - # Make the child a session-leader by detaching from the terminal - try: - pid = os.fork() - if pid != 0: - sys.exit(0) - except OSError, e: - raise RuntimeError("2st fork failed: %s [%d]" % - (e.strerror, e.errno)) - raise Exception, "%s [%d]" % (e.strerror, e.errno) - - dev_null = file('/dev/null', 'r') - os.dup2(dev_null.fileno(), sys.stdin.fileno()) - +#!/usr/bin/env python +# Author: Nic Wolfe +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see . + +import sys + +# we only need this for compiling an EXE and I will just always do that on 2.6+ +if sys.hexversion >= 0x020600F0: + from multiprocessing import freeze_support + +import locale +import os +import threading +import time +import signal +import traceback +import getopt + +import sickbeard + +from sickbeard import db +from sickbeard.tv import TVShow +from sickbeard import logger +from sickbeard.version import SICKBEARD_VERSION + +from sickbeard.webserveInit import initWebServer + +from lib.configobj import ConfigObj + +signal.signal(signal.SIGINT, sickbeard.sig_handler) +signal.signal(signal.SIGTERM, sickbeard.sig_handler) + +def loadShowsFromDB(): + + myDB = db.DBConnection() + sqlResults = myDB.select("SELECT * FROM tv_shows") + + for sqlShow in sqlResults: + try: + curShow = TVShow(int(sqlShow["tvdb_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) + + #TODO: make it update the existing shows if the showlist has something in it + +def daemonize(): + # Make a non-session-leader child process + try: + pid = os.fork() #@UndefinedVariable - only available in UNIX + if pid != 0: + sys.exit(0) + except OSError, e: + raise RuntimeError("1st fork failed: %s [%d]" % + (e.strerror, e.errno)) + + os.setsid() #@UndefinedVariable - only available in UNIX + + # Make sure I can read my own files and shut out others + prev = os.umask(0) + os.umask(prev and int('077',8)) + + # Make the child a session-leader by detaching from the terminal + try: + pid = os.fork() #@UndefinedVariable - only available in UNIX + if pid != 0: + sys.exit(0) + except OSError, e: + raise RuntimeError("2st fork failed: %s [%d]" % + (e.strerror, e.errno)) + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + dev_null = file('/dev/null', 'r') + os.dup2(dev_null.fileno(), sys.stdin.fileno()) + if sickbeard.CREATEPID: pid = str(os.getpid()) logger.log(u"Writing PID " + pid + " to " + str(sickbeard.PIDFILE)) file(sickbeard.PIDFILE, 'w').write("%s\n" % pid) -def main(): - - # do some preliminary stuff - sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(sys.argv[0])) - sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME) - sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME) - sickbeard.MY_ARGS = sys.argv[1:] +def main(): + + # do some preliminary stuff + sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(sys.argv[0])) + 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.CREATEPID = False - try: - locale.setlocale(locale.LC_ALL, "") - except (locale.Error, IOError): - pass - sickbeard.SYS_ENCODING = locale.getpreferredencoding() - - # for OSes that are poorly configured I'll just force UTF-8 - if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII'): - sickbeard.SYS_ENCODING = 'UTF-8' - - sickbeard.CONFIG_FILE = os.path.join(sickbeard.PROG_DIR, "config.ini") - - # 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=', 'tvbinz', 'pidfile=']) - except getopt.GetoptError: - print "Available options: --quiet, --forceupdate, --port, --daemon --pidfile" - sys.exit() - - forceUpdate = 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 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' + + # 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 + # for now we'll just silence the logging if o in ('-q', '--quiet'): - consoleLogging = False - # for now we'll just silence the logging - if o in ('--tvbinz'): - sickbeard.SHOW_TVBINZ = True - + consoleLogging = False + # should we update right away? - if o in ('-f', '--forceupdate'): + if o in ('-f', '--forceupdate'): forceUpdate = True - # use a different port - if o in ('-p', '--port'): - forcedPort = int(a) + # should we update right away? + if o in ('--nolaunch',): + noLaunch = True - # Run as a daemon + # use a different port + if o in ('-p', '--port'): + forcedPort = int(a) + + # Run as a daemon if o in ('-d', '--daemon'): - if sys.platform == 'win32': - print "Daemonize not supported under Windows, starting normally" - else: - consoleLogging = False - sickbeard.DAEMON = True - + if sys.platform == 'win32': + print "Daemonize not supported under Windows, starting normally" + else: + consoleLogging = False + sickbeard.DAEMON = True + + # config file + if o in ('--config',): + sickbeard.CONFIG_FILE = os.path.abspath(a) + + # datadir + if o in ('--datadir',): + sickbeard.DATA_DIR = os.path.abspath(a) + # write a pidfile if requested - if o in ('--pidfile'): + if o in ('--pidfile',): sickbeard.PIDFILE = str(a) # if the pidfile already exists, sickbeard may still be running, so exit @@ -176,96 +183,120 @@ def main(): raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno)) else: logger.log(u"Not running in daemon mode. PID file creation disabled.") - - if consoleLogging: - print "Starting up Sick Beard "+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 config.ini, all settings will be default", logger.ERROR) - - sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) - - # initialize the config and our threads - sickbeard.initialize(consoleLogging=consoleLogging) - - sickbeard.showList = [] - - 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 - - logger.log(u"Starting Sick Beard on http://localhost:"+str(startPort)) - - 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' - - try: - initWebServer({ - 'port': startPort, - 'host': webhost, - 'data_root': os.path.join(sickbeard.PROG_DIR, 'data'), - 'web_root': sickbeard.WEB_ROOT, - 'log_dir': log_dir, - 'username': sickbeard.WEB_USERNAME, - 'password': sickbeard.WEB_PASSWORD, - }) - except IOError: - logger.log(u"Unable to start web server, is something else running on port %d?" % startPort, logger.ERROR) - if sickbeard.LAUNCH_BROWSER: - logger.log(u"Launching browser and exiting", logger.ERROR) - sickbeard.launchBrowser(startPort) - sys.exit() - - # build from the DB to start with - logger.log(u"Loading initial show list") - loadShowsFromDB() - - # fire up all our threads - sickbeard.start() - - # launch browser if we're supposed to - if sickbeard.LAUNCH_BROWSER: - sickbeard.launchBrowser(startPort) - - # start an update if we're supposed to - if forceUpdate: - sickbeard.showUpdateScheduler.action.run(force=True) - - # stay alive while my threads do the work - while (True): - - if sickbeard.invoked_command: - logger.log(u"Executing invoked command: "+repr(sickbeard.invoked_command)) - sickbeard.invoked_command() - sickbeard.invoked_command = None - - time.sleep(1) - - return - -if __name__ == "__main__": - if sys.hexversion >= 0x020600F0: - freeze_support() - main() + # 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("Data dir 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") + + os.chdir(sickbeard.DATA_DIR) + + if consoleLogging: + print "Starting up Sick Beard "+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.WARNING) + + sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) + + # initialize the config and our threads + sickbeard.initialize(consoleLogging=consoleLogging) + + sickbeard.showList = [] + + 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 + + logger.log(u"Starting Sick Beard on http://localhost:"+str(startPort)) + + 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' + + try: + initWebServer({ + 'port': startPort, + 'host': webhost, + 'data_root': os.path.join(sickbeard.PROG_DIR, 'data'), + 'web_root': sickbeard.WEB_ROOT, + 'log_dir': log_dir, + 'username': sickbeard.WEB_USERNAME, + 'password': sickbeard.WEB_PASSWORD, + }) + except IOError: + logger.log(u"Unable to start web server, is something else running on port %d?" % startPort, logger.ERROR) + if sickbeard.LAUNCH_BROWSER: + logger.log(u"Launching browser and exiting", logger.ERROR) + sickbeard.launchBrowser(startPort) + sys.exit() + + # build from the DB to start with + logger.log(u"Loading initial show list") + loadShowsFromDB() + + # fire up all our threads + 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 + if forceUpdate: + sickbeard.showUpdateScheduler.action.run(force=True) #@UndefinedVariable + + # stay alive while my threads do the work + while (True): + + if sickbeard.invoked_command: + logger.log(u"Executing invoked command: "+repr(sickbeard.invoked_command)) + sickbeard.invoked_command() + sickbeard.invoked_command = None + + time.sleep(1) + + return + +if __name__ == "__main__": + if sys.hexversion >= 0x020600F0: + freeze_support() + main() diff --git a/data/css/config.css b/data/css/config.css index f52f8a54c7..45dc728101 100644 --- a/data/css/config.css +++ b/data/css/config.css @@ -12,6 +12,7 @@ #config-content{display:block;width:835px;text-align:left;clear:both;background:#fff;margin:0 auto;padding:0 0 40px;} #config-components{float:left;width:auto;} #config-components-border{float:left;width:auto;border-top:1px solid #999;padding:5px 0;} +#config .title-group{border-bottom:1px dotted #666;position:relative;padding:25px 15px 25px;} #config .component-group{border-bottom:1px dotted #666;position:relative;padding:15px 15px 25px;} #config .component-group-desc{float:left;width:235px;} #config .component-group-desc h3{font-size:1.5em;} @@ -57,4 +58,11 @@ .infoTableHeader, .infoTableCell {padding: 5px;} .infoTableHeader{font-weight:700;} -#config div.testNotification {border: 1px dotted #CCCCCC; padding: 5px; margin-bottom: 10px; line-height:20px;} \ No newline at end of file +#config div.testNotification {border: 1px dotted #CCCCCC; padding: 5px; margin-bottom: 10px; line-height:20px;} + +.config_message { + width: 100%; + text-align: center; + font-size: 1.3em; + background: #ffecac; +} \ No newline at end of file diff --git a/data/css/default.css b/data/css/default.css index 3435ae355a..f77ce6b0d8 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -2,7 +2,7 @@ img { border: 0; vertical-align: middle;} body { -background-color:#fff; +background-color:#F5F1E4; color:#000; font-family:'Verdana', 'Helvetica', 'Sans-serif', 'sans'; font-size:12px; @@ -36,6 +36,7 @@ display:inline; /* these are for incl_top.tmpl */ #header { +background-color:#fff; padding: 5px 0; z-index:2; } @@ -130,6 +131,7 @@ background-color:#F5F1E4; border-top:1px solid #b3b3b3; color:#4e4e4e; line-height: 1.4em; +font-size: 1em; } .sickbeardTable { @@ -349,4 +351,14 @@ div#addShowPortal button .buttontext { position: relative; display: block; padd .optionWrapper div.selectChoices { float: left; width: 175px; margin-left: 25px; } .optionWrapper br { clear: both; } -a.whitelink { color: white; } \ No newline at end of file +a.whitelink { color: white; } + +/* for displayShow notice */ +#show_message { + padding: 5px 8px; + background: #ffd575; + color: #333333; + text-shadow: 1px 1px 0 rgba(255,255,255,0.3); + font-size: 1em; +} +div.ui-pnotify { min-width: 340px; max-width: 550px; width: auto !important;} \ No newline at end of file diff --git a/data/images/loading16_dddddd.gif b/data/images/loading16_dddddd.gif new file mode 100644 index 0000000000..190582b822 Binary files /dev/null and b/data/images/loading16_dddddd.gif differ diff --git a/data/images/loading_posters.gif b/data/images/loading_posters.gif deleted file mode 100644 index 075aeba0f2..0000000000 Binary files a/data/images/loading_posters.gif and /dev/null differ diff --git a/data/images/providers/sick_beard_index.gif b/data/images/providers/sick_beard_index.gif new file mode 100644 index 0000000000..73f8ae5c2e Binary files /dev/null and b/data/images/providers/sick_beard_index.gif differ diff --git a/data/interfaces/default/config.tmpl b/data/interfaces/default/config.tmpl index a070c2fe39..cbf8318e21 100644 --- a/data/interfaces/default/config.tmpl +++ b/data/interfaces/default/config.tmpl @@ -1,4 +1,5 @@ #import sickbeard +#from sickbeard import db #import os.path #set global $title="Configuration" @@ -11,6 +12,7 @@ + diff --git a/data/interfaces/default/config_general.tmpl b/data/interfaces/default/config_general.tmpl index 2cbcd3975a..ee7bc51e4e 100644 --- a/data/interfaces/default/config_general.tmpl +++ b/data/interfaces/default/config_general.tmpl @@ -12,11 +12,13 @@ #set global $topmenu="config"# #include $os.path.join($sickbeard.PROG_DIR, "data/interfaces/default/inc_top.tmpl") + +
-
All non-absolute folder locations are relative to " $sickbeard.PROG_DIR "
+
All non-absolute folder locations are relative to " $sickbeard.DATA_DIR "
-
+
@@ -56,7 +58,7 @@
- +
@@ -119,11 +121,11 @@
- + -

+

diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl index d8a8596606..963be414ba 100644 --- a/data/interfaces/default/config_notifications.tmpl +++ b/data/interfaces/default/config_notifications.tmpl @@ -13,7 +13,7 @@
-
+
@@ -105,7 +105,7 @@
Click below to test.
- +
@@ -199,7 +199,7 @@
Click below to test.
- +
@@ -265,9 +265,9 @@
-
Click below to test.
- - +
Click below to register and test Growl, this is required for Growl notifications to work.
+ + @@ -342,7 +342,7 @@
Click below to test.
- + @@ -412,7 +412,7 @@
Click below to test.
- + @@ -476,7 +476,7 @@
Click below to test.
- + @@ -519,7 +519,7 @@
Click below to test.
- + @@ -589,12 +589,12 @@
Click below to test.
- + -

+

diff --git a/data/interfaces/default/config_postProcessing.tmpl b/data/interfaces/default/config_postProcessing.tmpl index eeb2434ab3..d8d0e21118 100644 --- a/data/interfaces/default/config_postProcessing.tmpl +++ b/data/interfaces/default/config_postProcessing.tmpl @@ -13,12 +13,13 @@ #include $os.path.join($sickbeard.PROG_DIR, "data/interfaces/default/inc_top.tmpl") +
-
All non-absolute folder locations are relative to " $sickbeard.PROG_DIR "
+
All non-absolute folder locations are relative to " $sickbeard.DATA_DIR "
- +
@@ -91,7 +92,7 @@
-
+
@@ -159,7 +160,7 @@
-
+
@@ -287,11 +288,11 @@ - + -

+

diff --git a/data/interfaces/default/config_providers.tmpl b/data/interfaces/default/config_providers.tmpl index ff951fe05a..5054118aa2 100644 --- a/data/interfaces/default/config_providers.tmpl +++ b/data/interfaces/default/config_providers.tmpl @@ -10,6 +10,7 @@ #include $os.path.join($sickbeard.PROG_DIR, "data/interfaces/default/inc_top.tmpl") + +
#if (len($seasonResults) > 14): @@ -150,10 +155,10 @@ $epLoc #end if -
+ diff --git a/data/interfaces/default/inc_top.tmpl b/data/interfaces/default/inc_top.tmpl index 2ebccfb271..66e13afc57 100644 --- a/data/interfaces/default/inc_top.tmpl +++ b/data/interfaces/default/inc_top.tmpl @@ -73,6 +73,7 @@ table.tablesorter thead tr .headerSortDown { background-image: url("$sbRoot/imag + + diff --git a/data/js/ajaxNotifications.js b/data/js/ajaxNotifications.js new file mode 100644 index 0000000000..3082035a90 --- /dev/null +++ b/data/js/ajaxNotifications.js @@ -0,0 +1,22 @@ +var message_url = sbRoot + '/ui/get_messages'; + +function check_notifications() { + $.getJSON(message_url, function(data){ + $.each(data, function(name,data){ + $.pnotify({ + pnotify_type: data.type, + pnotify_hide: data.type == 'notice', + pnotify_title: data.title, + pnotify_text: data.message + }); + }); + }); + + setTimeout(check_notifications, 3000) +} + +$(document).ready(function(){ + + check_notifications(); + +}); \ No newline at end of file diff --git a/data/js/config.js b/data/js/config.js index 37d3a24c13..c5a31e114a 100644 --- a/data/js/config.js +++ b/data/js/config.js @@ -10,4 +10,27 @@ $(document).ready(function(){ else $('#content_'+$(this).attr('id')).hide(); }); -}); \ No newline at end of file + + // bind 'myForm' and provide a simple callback function + $('#configForm').ajaxForm({ + beforeSubmit: function(){ + $('.config_submitter').each(function(){ + $(this).attr("disabled", "disabled"); + $(this).after(' Saving...'); + $(this).hide(); + }); + }, + success: function(){ + setTimeout('config_success()', 2000) + } + }); + +}); + +function config_success(){ + $('.config_submitter').each(function(){ + $(this).removeAttr("disabled"); + $(this).next().remove(); + $(this).show(); + }); +} \ No newline at end of file diff --git a/data/js/configProviders.js b/data/js/configProviders.js index 137b8ce0b4..3a6d3630a6 100644 --- a/data/js/configProviders.js +++ b/data/js/configProviders.js @@ -21,8 +21,11 @@ $(document).ready(function(){ var newData = [isDefault, [name, url, key]]; newznabProviders[id] = newData; - $('#editANewznabProvider').addOption(id, name); - $(this).populateNewznabSection(); + if (!isDefault) + { + $('#editANewznabProvider').addOption(id, name); + $(this).populateNewznabSection(); + } if ($('#provider_order_list > #'+id).length == 0 && showProvider != false) { var toAdd = '
  • '+name+' '+name+'
  • ' @@ -120,10 +123,22 @@ $(document).ready(function(){ } var newznabProviders = new Array(); + + $('.newznab_key').change(function(){ + + var provider_id = $(this).attr('id'); + provider_id = provider_id.substring(0, provider_id.length-'_hash'.length); + + var url = $('#'+provider_id+'_url').val(); + var key = $(this).val(); + + $(this).updateProvider(provider_id, url, key); + + }); $('#newznab_key').change(function(){ - var selectedProvider = $('#editANewznabProvider :selected').val(); + var selectedProvider = $('#editANewznabProvider :selected').val(); var url = $('#newznab_url').val(); var key = $('#newznab_key').val(); diff --git a/data/js/configSearch.js b/data/js/configSearch.js index 01d8f89bc8..6dba86d8a5 100644 --- a/data/js/configSearch.js +++ b/data/js/configSearch.js @@ -1,5 +1,12 @@ $(document).ready(function(){ + function toggle_torrent_title(){ + if ($('#use_torrents').attr('checked')) + $('#no-torrents').show(); + else + $('#no-torrents').hide(); + } + $.fn.nzb_method_handler = function() { var selectedProvider = $('#nzb_method :selected').val(); @@ -24,4 +31,10 @@ $(document).ready(function(){ $(this).nzb_method_handler(); + $('#use_torrents').click(function(){ + toggle_torrent_title(); + }); + + toggle_torrent_title(); + }); diff --git a/data/js/displayShow.js b/data/js/displayShow.js index d5e0ab2367..471244d5f9 100644 --- a/data/js/displayShow.js +++ b/data/js/displayShow.js @@ -1,12 +1,43 @@ $(document).ready(function(){ + $('.epSearch').click(function(){ + var parent = $(this).parent(); + + // put the ajax spinner (for non white bg) placeholder while we wait + parent.empty(); + parent.append($("").attr({"src": sbRoot+"/images/loading16_dddddd.gif", "height": "16", "alt": "", "title": "loading"})); + + $.getJSON($(this).attr('href'), function(data){ + // if they failed then just put the red X + if (data.result == 'failure') { + img_name = 'no16.png'; + img_result = 'failed'; + + // if the snatch was successful then apply the corresponding class and fill in the row appropriately + } else { + img_name = 'yes16.png'; + img_result = 'success'; + // color the row + parent.parent().removeClass('skipped wanted qual good unaired').addClass('good'); + parent.siblings('.status_column').html(data.result); + } + + // put the corresponding image as the result for the the row + parent.empty(); + parent.append($("").attr({"src": sbRoot+"/images/"+img_name, "height": "16", "alt": img_result, "title": img_result})); + }); + + // fon't follow the link + return false; + }); + $('#seasonJump').change(function() { - var id = $(this).val(); - if (id && id != 'jump') { - $('html,body').animate({scrollTop: $(id).offset().top},'slow'); - location.hash = id; - } - $(this).val('jump'); + var id = $(this).val(); + if (id && id != 'jump') { + $('html,body').animate({scrollTop: $(id).offset().top},'slow'); + location.hash = id; + } + $(this).val('jump'); }); $("#prevShow").click(function(){ @@ -20,7 +51,6 @@ $(document).ready(function(){ }); $('#changeStatus').click(function(){ - var sbRoot = $('#sbRoot').val() var epArr = new Array() @@ -36,13 +66,11 @@ $(document).ready(function(){ return false url = sbRoot+'/home/setStatus?show='+$('#showID').attr('value')+'&eps='+epArr.join('|')+'&status='+$('#statusSelect').attr('value') - window.location.href = url }); $('.seasonCheck').click(function(){ - var seasCheck = this; var seasNo = $(seasCheck).attr('id'); @@ -50,8 +78,8 @@ $(document).ready(function(){ var epParts = $(this).attr('id').split('x') if (epParts[0] == seasNo) { - this.checked = seasCheck.checked - } + this.checked = seasCheck.checked + } }); }); diff --git a/data/js/jquery.form-2.69.js b/data/js/jquery.form-2.69.js new file mode 100644 index 0000000000..3ad71f891d --- /dev/null +++ b/data/js/jquery.form-2.69.js @@ -0,0 +1,815 @@ +/*! + * jQuery Form Plugin + * version: 2.69 (06-APR-2011) + * @requires jQuery v1.3.2 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +;(function($) { + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are intended to be exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').bind('submit', function(e) { + e.preventDefault(); // <-- important + $(this).ajaxSubmit({ + target: '#output' + }); + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + if (typeof options == 'function') { + options = { success: options }; + } + + var action = this.attr('action'); + var url = (typeof action === 'string') ? $.trim(action) : ''; + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + url = url || window.location.href || ''; + + options = $.extend(true, { + url: url, + success: $.ajaxSettings.success, + type: this[0].getAttribute('method') || 'GET', // IE7 massage (see issue 57) + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var n,v,a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) { + a.push( { name: n, value: options.data[n][k] } ); + } + } + else { + v = options.data[n]; + v = $.isFunction(v) ? v() : v; // if value is fn, invoke it + a.push( { name: n, value: v } ); + } + } + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else { + options.data = q; // data is the query string for 'post' + } + + var $form = this, callbacks = []; + if (options.resetForm) { + callbacks.push(function() { $form.resetForm(); }); + } + if (options.clearForm) { + callbacks.push(function() { $form.clearForm(); }); + } + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + var fn = options.replaceTarget ? 'replaceWith' : 'html'; + $(options.target)[fn](data).each(oldSuccess, arguments); + }); + } + else if (options.success) { + callbacks.push(options.success); + } + + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg + var context = options.context || options; // jQuery 1.4+ supports scope context + for (var i=0, max=callbacks.length; i < max; i++) { + callbacks[i].apply(context, [data, status, xhr || $form, $form]); + } + }; + + // are there files to upload? + var fileInputs = $('input:file', this).length > 0; + var mp = 'multipart/form-data'; + var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if (options.iframe !== false && (fileInputs || options.iframe || multipart)) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) { + $.get(options.closeKeepAlive, fileUpload); + } + else { + fileUpload(); + } + } + else { + $.ajax(options); + } + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + + if ($(':input[name=submit],:input[id=submit]', form).length) { + // if there is an input with a name or id of 'submit' then we won't be + // able to invoke the submit fn on the form (at least not x-browser) + alert('Error: Form elements must not have name or id of "submit".'); + return; + } + + var s = $.extend(true, {}, $.ajaxSettings, options); + s.context = s.context || s; + var id = 'jqFormIO' + (new Date().getTime()), fn = '_'+id; + var $io = $('
    SB Version: alpha ($sickbeard.version.SICKBEARD_VERSION)
    SB Config file: $sickbeard.CONFIG_FILE
    SB Database file: $db.dbFilename()
    SB Cache Dir: $sickbeard.CACHE_DIR
    SB Arguments: $sickbeard.MY_ARGS
    SB Web Root: $sickbeard.WEB_ROOT
    $statusStrings[int($epResult["status"])]$statusStrings[int($epResult["status"])] - #if int($epResult["season"]) != 0: - search + #if int($epResult["season"]) != 0: + search #end if