diff --git a/.gitignore b/.gitignore
index 78c1cef926a87528ce6f1902a6200f43e784aeca..8841fd4166c10f411d50e8b5aae5f62ce0c761b8 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 ba46efc0a3ab8e055b797fc723d64c3aa261e280..6aab1d93aecb500daa6b2dcd8470e0801e2f44dd 100755
--- a/SickBeard.py
+++ b/SickBeard.py
@@ -1,165 +1,172 @@
-#!/usr/bin/env python
-# Author: Nic Wolfe <nic@wolfeden.ca>
-# 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 <http://www.gnu.org/licenses/>.
-
-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 <nic@wolfeden.ca>
+# 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 <http://www.gnu.org/licenses/>.
+
+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 f52f8a54c70c4ef51e32d64bf9110be7ad261276..45dc728101f8db1cd6c7668f6b478806b5a84b16 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 3435ae355a20a9211f623f4876ac3bf507b38c8c..f77ce6b0d8c779535bb647459586ebbba516c59c 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 0000000000000000000000000000000000000000..190582b822618e226f1beed6cee1135fcfaf5984
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 075aeba0f2d57e8080fb2e873e8ced4e601a3459..0000000000000000000000000000000000000000
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 0000000000000000000000000000000000000000..73f8ae5c2e25e640e31286578607e692946a32c4
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 a070c2fe3964a13a6526477e5d040d014b31aed2..cbf8318e21d5bb4ef4259412650e5d8598f60a64 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 @@
 <table class="infoTable" cellspacing="1" border="0" cellpadding="0">
     <tr><td class="infoTableHeader">SB Version: </td><td class="infoTableCell">alpha ($sickbeard.version.SICKBEARD_VERSION) <!-- &ndash; build.date //--></td></tr>
     <tr><td class="infoTableHeader">SB Config file: </td><td class="infoTableCell">$sickbeard.CONFIG_FILE</td></tr>
+    <tr><td class="infoTableHeader">SB Database file: </td><td class="infoTableCell">$db.dbFilename()</td></tr>
     <tr><td class="infoTableHeader">SB Cache Dir: </td><td class="infoTableCell">$sickbeard.CACHE_DIR</td></tr>
     <tr><td class="infoTableHeader">SB Arguments: </td><td class="infoTableCell">$sickbeard.MY_ARGS</td></tr>
     <tr><td class="infoTableHeader">SB Web Root: </td><td class="infoTableCell">$sickbeard.WEB_ROOT</td></tr>
diff --git a/data/interfaces/default/config_general.tmpl b/data/interfaces/default/config_general.tmpl
index 2cbcd3975a4e520779b08ab7cc85ec3e688a1ffe..ee7bc51e4e2c04a68142c97402657964b7065083 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")
 
+<script type="text/javascript" src="$sbRoot/js/config.js"></script>
+
 <div id="config">
 <div id="config-content">
-<h5>All non-absolute folder locations are relative to " <span class="path">$sickbeard.PROG_DIR</span> "</h5>
+<h5>All non-absolute folder locations are relative to " <span class="path">$sickbeard.DATA_DIR</span> "</h5>
 
-<form action="saveGeneral" method="post">
+<form id="configForm" action="saveGeneral" method="post">
 
             <div id="config-components">
                 
@@ -56,7 +58,7 @@
                             </label>
                         </div>
 
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                     </fieldset>
                 </div><!-- /component-group1 //-->
 
@@ -119,11 +121,11 @@
                             </label>
                         </div>
 
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                     </fieldset>
                 </div><!-- /component-group3 //-->
 
-            <br/><input type="submit" value="Save Changes" /><br/>
+            <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/>
             </div><!-- /config-components -->
 
 </form>
diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl
index d8a8596606b42b4e51140305456ed02f5ab18ce2..963be414baa0d4d3f26eab2fb0fcb071a371b4e5 100644
--- a/data/interfaces/default/config_notifications.tmpl
+++ b/data/interfaces/default/config_notifications.tmpl
@@ -13,7 +13,7 @@
 <div id="config">
 <div id="config-content">
 
-<form id="form" action="saveNotifications" method="post">
+<form id="configForm" action="saveNotifications" method="post">
 
             <div id="config-components">
 
@@ -105,7 +105,7 @@
 
                         <div class="testNotification" id="testXBMC-result">Click below to test.</div>
                         <input type="button" value="Test XBMC" id="testXBMC" />
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
 
                         </div><!-- /enabler_xbmc //-->
 
@@ -199,7 +199,7 @@
 
                         <div class="testNotification" id="testPLEX-result">Click below to test.</div>
                         <input type="button" value="Test Plex Media Server" id="testPLEX" />
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
 
                         </div><!-- /enabler_plex -->
 
@@ -265,9 +265,9 @@
                             </label>
                         </div>
 
-                        <div class="testNotification" id="testGrowl-result">Click below to test.</div>
-                        <input type="button" value="Test Growl" id="testGrowl" />
-                        <input type="submit" value="Save Changes" />
+                        <div class="testNotification" id="testGrowl-result">Click below to register and test Growl, this is required for Growl notifications to work.</div>
+                        <input type="button" value="Register Growl" id="testGrowl" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                         
                         </div><!-- /content_use_growl //-->
 
@@ -342,7 +342,7 @@
 
                         <div class="testNotification" id="testTwitter-result">Click below to test.</div>
                         <input type="button" value="Test Twitter" id="testTwitter" />
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                         
                         </div><!-- /content_use_twitter //-->
 
@@ -412,7 +412,7 @@
 
                         <div class="testNotification" id="testProwl-result">Click below to test.</div>
                         <input type="button" value="Test Prowl" id="testProwl" />
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                         
                         </div><!-- /content_use_prowl //-->
 
@@ -476,7 +476,7 @@
 
                         <div class="testNotification" id="testNotifo-result">Click below to test.</div>
                         <input type="button" value="Test Notifo" id="testNotifo" />
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                         
                         </div><!-- /content_use_notifo //-->
 
@@ -519,7 +519,7 @@
 
                         <div class="testNotification" id="testLibnotify-result">Click below to test.</div>
                         <input type="button" value="Test Libnotify" id="testLibnotify" />
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
 
                         </div><!-- /content_use_libnotify //-->
 
@@ -589,12 +589,12 @@
 
                         <div class="testNotification" id="testNMJ-result">Click below to test.</div>
                         <input type="button" value="Test NMJ" id="testNMJ" />
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                         </div><!-- /content_use_nmj //-->
                     </fieldset>
                 </div><!-- /component-group //-->
 
-            <br/><input type="submit" value="Save Changes" /><br/>
+            <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/>
 
             </div><!-- /config-components //-->
 
diff --git a/data/interfaces/default/config_postProcessing.tmpl b/data/interfaces/default/config_postProcessing.tmpl
index eeb2434ab3ee76570f9021755d3e4c93275f6b4b..d8d0e211182ea84c5d25df1352a1fd604ee896ae 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")
 
 <script type="text/javascript" src="$sbRoot/js/configPostProcessing.js"></script>
+<script type="text/javascript" src="$sbRoot/js/config.js"></script>
 
 <div id="config">
 <div id="config-content">
-<h5>All non-absolute folder locations are relative to " <span class="path">$sickbeard.PROG_DIR</span> "</h5>
+<h5>All non-absolute folder locations are relative to " <span class="path">$sickbeard.DATA_DIR</span> "</h5>
 
-<form action="savePostProcessing" method="post">
+<form id="configForm" action="savePostProcessing" method="post">
 
             <div id="config-components">
                 
@@ -91,7 +92,7 @@
                         </div>
 
                         <div class="clearfix"></div>
-                        <input type="submit" value="Save Changes" /><br/>
+                        <input type="submit" class="config_submitter" value="Save Changes" /><br/>
 
                     </fieldset>
                 </div><!-- /component-group3 //-->
@@ -159,7 +160,7 @@
                             </label>
                         </div>
 
-                        <input type="submit" value="Save Changes" /><br/>
+                        <input type="submit" class="config_submitter" value="Save Changes" /><br/>
 
                     </fieldset>
                 </div><!-- /component-group2 //-->
@@ -287,11 +288,11 @@
                             </label>
                         </div>
 
-                        <input type="submit" value="Save Changes" />
+                        <input type="submit" class="config_submitter" value="Save Changes" />
                     </fieldset>
                 </div><!-- /component-group4 //-->
 
-            <br/><input type="submit" value="Save Changes" /><br/>
+            <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/>
             </div><!-- /config-components -->
 
 </form>
diff --git a/data/interfaces/default/config_providers.tmpl b/data/interfaces/default/config_providers.tmpl
index ff951fe05ab3e7091c264b67a973cb73d0b6c1ee..5054118aa2a915b7527272784c500364b9210034 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")
 
 <script type="text/javascript" src="$sbRoot/js/configProviders.js"></script>
+<script type="text/javascript" src="$sbRoot/js/config.js"></script>
 <script type="text/javascript" charset="utf-8">
 <!--
 \$(document).ready(function(){
@@ -25,7 +26,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
 <div id="config">
 <div id="config-content">
 
-<form action="saveProviders" method="post">
+<form id="configForm" action="saveProviders" method="post">
 
             <div id="config-components">
                 
@@ -43,9 +44,6 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                     <fieldset class="component-group-list">
                         <ul id="provider_order_list">
                         #for $curProvider in $sickbeard.providers.sortedProviderList():
-                            #if $curProvider.getID() == 'tvbinz' and not $sickbeard.SHOW_TVBINZ
-                                #continue
-                            #end if
                             #if $curProvider.providerType == $GenericProvider.NZB and not $sickbeard.USE_NZBS:
                                 #continue
                             #elif $curProvider.providerType == $GenericProvider.TORRENT and not $sickbeard.USE_TORRENTS:
@@ -62,14 +60,14 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                         #end for
                         </ul>
                         <input type="hidden" name="provider_order" id="provider_order" value="<%=" ".join([x.getID()+':'+str(int(x.isEnabled())) for x in sickbeard.providers.sortedProviderList()])%>"/>
-                        <br/><input type="submit" value="Save Changes" /><br/>
+                        <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/>
                     </fieldset>
                 </div><!-- /component-group1 //-->
 
                 <div id="core-component-group2" class="component-group clearfix">
 
                     <div class="component-group-desc">
-                        <h3>Configure Providers</h3>
+                        <h3>Configure Built-In<br />Providers</h3>
                         <p>Check with provider's website on how to obtain an API key if needed.</p>
                     </div>
                     
@@ -79,11 +77,8 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                                 <span class="component-title jumbo">Configure Provider:</span>
                                 <span class="component-desc">
                                     #set $provider_config_list = []
-                                    #for $cur_provider in ("nzbs_org", "nzbs_r_us", "newzbin", "nzbmatrix", "tvbinz", "tvtorrents"):
+                                    #for $cur_provider in ("nzbs_org", "nzbs_r_us", "newzbin", "nzbmatrix", "tvtorrents"):
                                         #set $cur_provider_obj = $sickbeard.providers.getProviderClass($cur_provider)
-                                        #if $cur_provider_obj.getID() == 'tvbinz' and not $sickbeard.SHOW_TVBINZ
-                                            #continue
-                                        #end if
                                         #if $cur_provider_obj.providerType == $GenericProvider.NZB and not $sickbeard.USE_NZBS:
                                             #continue
                                         #elif $cur_provider_obj.providerType == $GenericProvider.TORRENT and not $sickbeard.USE_TORRENTS:
@@ -94,7 +89,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
 
                                     #if $provider_config_list:                                        
                                     <select id="editAProvider">
-                                        #for $cur_provider in $provider_config_list:
+                                        #for $cur_provider in $provider_config_list + [$curProvider for $curProvider in $sickbeard.newznabProviderList if $curProvider.default and $curProvider.needs_auth]:
                                             <option value="$cur_provider.getID()">$cur_provider.name</option>
                                         #end for
                                     </select>
@@ -107,28 +102,23 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
 
 
 <!-- start div for editing providers //-->
-#if $sickbeard.SHOW_TVBINZ:
-<div class="providerDiv" id="tvbinzDiv">
-                        <div class="field-pair">
-                            <label class="clearfix">
-                                <span class="component-title">TVBinz Cookie UID</span>
-                                <input class="component-desc" type="text" name="tvbinz_uid" value="$sickbeard.TVBINZ_UID" size="10" />
-                            </label>
-                        </div>
+#for $curNewznabProvider in [$curProvider for $curProvider in $sickbeard.newznabProviderList if $curProvider.default and $curProvider.needs_auth]:
+<div class="providerDiv" id="${curNewznabProvider.getID()}Div">
                         <div class="field-pair">
                             <label class="clearfix">
-                                <span class="component-title">TVBinz Hash</span>
-                                <input class="component-desc" type="text" name="tvbinz_hash" value="$sickbeard.TVBINZ_HASH" size="40" />
+                                <span class="component-title">$curNewznabProvider.name URL</span>
+                                <input class="component-desc" type="text" id="${curNewznabProvider.getID()}_url" value="$curNewznabProvider.url" size="40" disabled/>
                             </label>
                         </div>
                         <div class="field-pair">
                             <label class="clearfix">
-                                <span class="component-title">TVBinz Auth String</span>
-                                <input class="component-desc" type="text" name="tvbinz_auth" value="$sickbeard.TVBINZ_AUTH" size="40" />
+                                <span class="component-title">$curNewznabProvider.name API Key</span>
+                                <input class="component-desc newznab_key" type="text" id="${curNewznabProvider.getID()}_hash" value="$curNewznabProvider.key" size="40" />
                             </label>
                         </div>
 </div>
-#end if
+#end for
+
 
 <div class="providerDiv" id="nzbs_orgDiv">
                         <div class="field-pair">
@@ -207,7 +197,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
 
 <!-- end div for editing providers -->
 
-                    <input type="submit" value="Save Changes" /><br/>
+                    <input type="submit" class="config_submitter" value="Save Changes" /><br/>
             
                     </fieldset>
                 </div><!-- /component-group2 //-->
@@ -215,9 +205,9 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                 <div id="core-component-group3" class="component-group clearfix">
 
                     <div class="component-group-desc">
-                        <h3>Newznab Providers</h3>
-                        <p>Add and setup newznab providers.</p>
-                        <p>NZB.su has already been added as an example and can not be deleted.</p>
+                        <h3>Configure Custom<br />Newznab Providers</h3>
+                        <p>Add and setup custom Newznab providers.</p>
+                        <p>Some built-in Newznab providers are already available above.</p>
                     </div>
 
                     <fieldset class="component-group-list">
@@ -268,7 +258,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                 </div><!-- /component-group3 //-->
 
                     
-            <br/><input type="submit" value="Save Changes" /><br/>
+            <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/>
                 
             </div><!-- /config-components //-->
 
diff --git a/data/interfaces/default/config_search.tmpl b/data/interfaces/default/config_search.tmpl
index 1d7b64f42d4df46bc0189d96dbfa039837b7cb8a..600b29f1440e9154accff420629b793f4cb9d4fa 100644
--- a/data/interfaces/default/config_search.tmpl
+++ b/data/interfaces/default/config_search.tmpl
@@ -13,8 +13,9 @@
 
 <div id="config">
 <div id="config-content">
-<h5>All non-absolute folder locations are relative to " <span class="path">$sickbeard.PROG_DIR</span> "</h5>
-<form action="saveSearch" method="post">
+<h5>All non-absolute folder locations are relative to " <span class="path">$sickbeard.DATA_DIR</span> "</h5>
+
+<form id="configForm" action="saveSearch" method="post">
 
             <div id="config-components">
                 
@@ -57,7 +58,7 @@
                         </div>
 
                         <div class="clearfix"></div>
-                        <input type="submit" value="Save Changes" /><br/>
+                        <input type="submit" class="config_submitter" value="Save Changes" /><br/>
                         
                     </fieldset>
                 </div><!-- /component-group1 //-->
@@ -213,13 +214,17 @@
                         </div>
                         
                         <div class="clearfix"></div>
-                        <input type="submit" value="Save Changes" /><br/>
+                        <input type="submit" class="config_submitter" value="Save Changes" /><br/>
                         
                         </div><!-- /content_use_nzbs //-->
                         
                     </fieldset>
                 </div><!-- /component-group2 //-->
 
+                <div class="title-group clearfix" id="no-torrents">
+                    <div class="ui-corner-all config_message">Note: Sick Beard works better with Usenet than with Torrents, <a href="http://www.sickbeard.com/usenet.html" target="_blank">here's why</a>.</div>
+                </div>
+
                 <div id="core-component-group3" class="component-group clearfix">
 
                     <div class="component-group-desc">
@@ -250,13 +255,13 @@
                         </div>
                         
                         <div class="clearfix"></div>
-                        <input type="submit" value="Save Changes" /><br/>
+                        <input type="submit" class="config_submitter" value="Save Changes" /><br/>
                         </div><!-- /content_use_torrents //-->
                         
                     </fieldset>
                 </div><!-- /component-group3 //-->
 
-            <br/><input type="submit" value="Save Changes" /><br/>
+            <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/>
 
             </div><!-- /config-components //-->
 
diff --git a/data/interfaces/default/displayShow.tmpl b/data/interfaces/default/displayShow.tmpl
index d1e0bd3900c351543ee245817d2808e96988509a..4cfed8cdb63f41d8a1831f8f934c4e200cabb7a0 100644
--- a/data/interfaces/default/displayShow.tmpl
+++ b/data/interfaces/default/displayShow.tmpl
@@ -11,6 +11,7 @@
 
 <script type="text/javascript" src="$sbRoot/js/jquery.bookmarkscroll.js"></script>
 
+
 <div class="h2footer align-right">
 #if (len($seasonResults) > 14):
     <select id="seasonJump">
@@ -34,6 +35,10 @@
 #end if
 </div><br/>
 
+#if $show_message:
+    <div id="show_message" class="ui-corner-all">$show_message</div><br />
+#end if
+
 <input type="hidden" id="sbRoot" value="$sbRoot" />
 
 <script type="text/javascript" src="$sbRoot/js/displayShow.js"></script>
@@ -150,10 +155,10 @@ $epLoc
 #end if
 
     </td>
-    <td>$statusStrings[int($epResult["status"])]</td>
+    <td class="status_column">$statusStrings[int($epResult["status"])]</td>
     <td align="center">
-    #if int($epResult["season"]) != 0: 
-    <a href="searchEpisode?show=$show.tvdbid&amp;season=$epResult["season"]&amp;episode=$epResult["episode"]"><img src="$sbRoot/images/search32.png" height="16" alt="search" title="Manual Search" /></a>
+    #if int($epResult["season"]) != 0:
+      <a class="epSearch" href="searchEpisode?show=$show.tvdbid&amp;season=$epResult["season"]&amp;episode=$epResult["episode"]"><img src="$sbRoot/images/search32.png" height="16" alt="search" title="Manual Search" /></a>
     #end if
     </td>
    </tr>
diff --git a/data/interfaces/default/inc_top.tmpl b/data/interfaces/default/inc_top.tmpl
index 2ebccfb271aa64f25f8f27b70718b842c137c7dd..66e13afc5704623cc3c2efd1ae2ff19652dd967c 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
     <script type="text/javascript" src="$sbRoot/js/tools.tooltip-1.2.5.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.pnotify-1.0.1.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.expand-1.3.8.js"></script>
+    <script type="text/javascript" src="$sbRoot/js/jquery.form-2.69.js"></script>
 
     <script type="text/javascript" charset="utf-8">
     <!--
@@ -91,6 +92,7 @@ table.tablesorter thead tr .headerSortDown { background-image: url("$sbRoot/imag
     </script>
     <script type="text/javascript" src="$sbRoot/js/jquery.scrolltopcontrol-1.1.js"></script>
     <script type="text/javascript" src="$sbRoot/js/browser.js"></script>
+    <script type="text/javascript" src="$sbRoot/js/ajaxNotifications.js"></script>
     
 <script type="text/javascript">
 <!--
@@ -139,22 +141,6 @@ table.tablesorter thead tr .headerSortDown { background-image: url("$sbRoot/imag
     \$.pnotify.defaults.pnotify_history = false;
     \$.pnotify.defaults.pnotify_delay = 4000;
 
-    #for $curMessage in $flash.messages():
-        \$.pnotify({
-            pnotify_title: decodeURIComponent('$urllib.quote($curMessage[0].encode('UTF-8'))'),
-            pnotify_text: decodeURIComponent('$urllib.quote($curMessage[1].encode('UTF-8'))')
-        });
-    #end for
-
-    #for $curError in $flash.errors():
-        \$.pnotify({
-            pnotify_type: 'error',
-            pnotify_hide: false,
-            pnotify_title: decodeURIComponent('$urllib.quote($curError[0].encode('UTF-8'))'),
-            pnotify_text: decodeURIComponent('$urllib.quote($curError[1].encode('UTF-8'))')
-        });
-    #end for
-
     });
 //-->
 </script>
diff --git a/data/js/ajaxNotifications.js b/data/js/ajaxNotifications.js
new file mode 100644
index 0000000000000000000000000000000000000000..3082035a908d9603ed0f97df0dfd956f4b5fb04f
--- /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 37d3a24c1344320e075ae0ad44238afdaf363d32..c5a31e114ae6cca3297e23ae3e3b75bf5cab8d01 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('<span><img src="'+sbRoot+'/images/loading16.gif"> Saving...</span>');
+    			$(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 137b8ce0b402b424856196f2071b349f977a9d8b..3a6d3630a6ce3ffa1fda40ba1cf5045c598980e6 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 = '<li class="ui-state-default" id="'+id+'"> <input type="checkbox" id="enable_'+id+'" class="provider_enabler" CHECKED> <a href="'+url+'" class="imgLink" target="_new"><img src="'+sbRoot+'/images/providers/newznab.gif" alt="'+name+'" width="16" height="16"></a> '+name+'</li>'
@@ -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 01d8f89bc8beb40a835b97a29c4f1278998deb26..6dba86d8a5826571e6374c428496f0bb8f9d6adb 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 d5e0ab23670afd7d3b7dce40df9cd52b400dbcbf..471244d5f9fb540611314c072021ad11e5d32667 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($("<img/>").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($("<img/>").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 0000000000000000000000000000000000000000..3ad71f891d3338cd39ba4b69b23d0c3d43bb3992
--- /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 = $('<iframe id="' + id + '" name="' + id + '" src="'+ s.iframeSrc +'" />');
+		var io = $io[0];
+
+		$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+
+		var xhr = { // mock object
+			aborted: 0,
+			responseText: null,
+			responseXML: null,
+			status: 0,
+			statusText: 'n/a',
+			getAllResponseHeaders: function() {},
+			getResponseHeader: function() {},
+			setRequestHeader: function() {},
+			abort: function() {
+				log('aborting upload...');
+				var e = 'aborted';
+				this.aborted = 1;
+				$io.attr('src', s.iframeSrc); // abort op in progress
+				xhr.error = e;
+				s.error && s.error.call(s.context, xhr, 'error', e);
+				g && $.event.trigger("ajaxError", [xhr, s, e]);
+				s.complete && s.complete.call(s.context, xhr, 'error');
+			}
+		};
+
+		var g = s.global;
+		// trigger ajax global events so that activity/block indicators work like normal
+		if (g && ! $.active++) {
+			$.event.trigger("ajaxStart");
+		}
+		if (g) {
+			$.event.trigger("ajaxSend", [xhr, s]);
+		}
+
+		if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
+			if (s.global) { 
+				$.active--;
+			}
+			return;
+		}
+		if (xhr.aborted) {
+			return;
+		}
+
+		var timedOut = 0;
+
+		// add submitting element to data if we know it
+		var sub = form.clk;
+		if (sub) {
+			var n = sub.name;
+			if (n && !sub.disabled) {
+				s.extraData = s.extraData || {};
+				s.extraData[n] = sub.value;
+				if (sub.type == "image") {
+					s.extraData[n+'.x'] = form.clk_x;
+					s.extraData[n+'.y'] = form.clk_y;
+				}
+			}
+		}
+
+		// take a breath so that pending repaints get some cpu time before the upload starts
+		function doSubmit() {
+			// make sure form attrs are set
+			var t = $form.attr('target'), a = $form.attr('action');
+
+			// update form attrs in IE friendly way
+			form.setAttribute('target',id);
+			if (form.getAttribute('method') != 'POST') {
+				form.setAttribute('method', 'POST');
+			}
+			if (form.getAttribute('action') != s.url) {
+				form.setAttribute('action', s.url);
+			}
+
+			// ie borks in some cases when setting encoding
+			if (! s.skipEncodingOverride) {
+				$form.attr({
+					encoding: 'multipart/form-data',
+					enctype:  'multipart/form-data'
+				});
+			}
+
+			// support timout
+			if (s.timeout) {
+				setTimeout(function() { timedOut = true; cb(); }, s.timeout);
+			}
+
+			// add "extra" data to form if provided in options
+			var extraInputs = [];
+			try {
+				if (s.extraData) {
+					for (var n in s.extraData) {
+						extraInputs.push(
+							$('<input type="hidden" name="'+n+'" value="'+s.extraData[n]+'" />')
+								.appendTo(form)[0]);
+					}
+				}
+
+				// add iframe to doc and submit the form
+				$io.appendTo('body');
+                io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+				form.submit();
+			}
+			finally {
+				// reset attrs and remove "extra" input elements
+				form.setAttribute('action',a);
+				if(t) {
+					form.setAttribute('target', t);
+				} else {
+					$form.removeAttr('target');
+				}
+				$(extraInputs).remove();
+			}
+		}
+
+		if (s.forceSync) {
+			doSubmit();
+		}
+		else {
+			setTimeout(doSubmit, 10); // this lets dom updates render
+		}
+	
+		var data, doc, domCheckCount = 50;
+
+		function cb() {
+			if (xhr.aborted) {
+				return;
+			}
+			
+			var doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
+			if (!doc || doc.location.href == s.iframeSrc) {
+				// response not received yet
+				if (!timedOut)
+					return;
+			}
+            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+			var ok = true;
+			try {
+				if (timedOut) {
+					throw 'timeout';
+				}
+
+				var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
+				log('isXml='+isXml);
+				if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
+					if (--domCheckCount) {
+						// in some browsers (Opera) the iframe DOM is not always traversable when
+						// the onload callback fires, so we loop a bit to accommodate
+						log('requeing onLoad callback, DOM not available');
+						setTimeout(cb, 250);
+						return;
+					}
+					// let this fall through because server response could be an empty document
+					//log('Could not access iframe DOM after mutiple tries.');
+					//throw 'DOMException: not available';
+				}
+
+				//log('response detected');
+				xhr.responseText = doc.body ? doc.body.innerHTML : doc.documentElement ? doc.documentElement.innerHTML : null; 
+				xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+				xhr.getResponseHeader = function(header){
+					var headers = {'content-type': s.dataType};
+					return headers[header];
+				};
+
+				var scr = /(json|script)/.test(s.dataType);
+				if (scr || s.textarea) {
+					// see if user embedded response in textarea
+					var ta = doc.getElementsByTagName('textarea')[0];
+					if (ta) {
+						xhr.responseText = ta.value;
+					}
+					else if (scr) {
+						// account for browsers injecting pre around json response
+						var pre = doc.getElementsByTagName('pre')[0];
+						var b = doc.getElementsByTagName('body')[0];
+						if (pre) {
+							xhr.responseText = pre.textContent;
+						}
+						else if (b) {
+							xhr.responseText = b.innerHTML;
+						}
+					}			  
+				}
+				else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
+					xhr.responseXML = toXml(xhr.responseText);
+				}
+				
+				data = httpData(xhr, s.dataType, s);
+			}
+			catch(e){
+				log('error caught:',e);
+				ok = false;
+				xhr.error = e;
+				s.error && s.error.call(s.context, xhr, 'error', e);
+				g && $.event.trigger("ajaxError", [xhr, s, e]);
+			}
+			
+			if (xhr.aborted) {
+				log('upload aborted');
+				ok = false;
+			}
+
+			// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+			if (ok) {
+				s.success && s.success.call(s.context, data, 'success', xhr);
+				g && $.event.trigger("ajaxSuccess", [xhr, s]);
+			}
+			
+			g && $.event.trigger("ajaxComplete", [xhr, s]);
+
+			if (g && ! --$.active) {
+				$.event.trigger("ajaxStop");
+			}
+			
+			s.complete && s.complete.call(s.context, xhr, ok ? 'success' : 'error');
+
+			// clean up
+			setTimeout(function() {
+				$io.removeData('form-plugin-onload');
+				$io.remove();
+				xhr.responseXML = null;
+			}, 100);
+		}
+
+		var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
+			if (window.ActiveXObject) {
+				doc = new ActiveXObject('Microsoft.XMLDOM');
+				doc.async = 'false';
+				doc.loadXML(s);
+			}
+			else {
+				doc = (new DOMParser()).parseFromString(s, 'text/xml');
+			}
+			return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
+		};
+		var parseJSON = $.parseJSON || function(s) {
+			return window['eval']('(' + s + ')');
+		};
+		
+		var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
+			var ct = xhr.getResponseHeader('content-type') || '',
+				xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
+				data = xml ? xhr.responseXML : xhr.responseText;
+
+			if (xml && data.documentElement.nodeName === 'parsererror') {
+				$.error && $.error('parsererror');
+			}
+			if (s && s.dataFilter) {
+				data = s.dataFilter(data, type);
+			}
+			if (typeof data === 'string') {
+				if (type === 'json' || !type && ct.indexOf('json') >= 0) {
+					data = parseJSON(data);
+				} else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
+					$.globalEval(data);
+				}
+			}
+			return data;
+		};
+	}
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ *	is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ *	used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+	// in jQuery 1.3+ we can fix mistakes with the ready state
+	if (this.length === 0) {
+		var o = { s: this.selector, c: this.context };
+		if (!$.isReady && o.s) {
+			log('DOM not ready, queuing ajaxForm');
+			$(function() {
+				$(o.s,o.c).ajaxForm(options);
+			});
+			return this;
+		}
+		// is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
+		log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
+		return this;
+	}
+	
+	return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
+		if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
+			e.preventDefault();
+			$(this).ajaxSubmit(options);
+		}
+	}).bind('click.form-plugin', function(e) {
+		var target = e.target;
+		var $el = $(target);
+		if (!($el.is(":submit,input:image"))) {
+			// is this a child element of the submit el?  (ex: a span within a button)
+			var t = $el.closest(':submit');
+			if (t.length == 0) {
+				return;
+			}
+			target = t[0];
+		}
+		var form = this;
+		form.clk = target;
+		if (target.type == 'image') {
+			if (e.offsetX != undefined) {
+				form.clk_x = e.offsetX;
+				form.clk_y = e.offsetY;
+			} else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+				var offset = $el.offset();
+				form.clk_x = e.pageX - offset.left;
+				form.clk_y = e.pageY - offset.top;
+			} else {
+				form.clk_x = e.pageX - target.offsetLeft;
+				form.clk_y = e.pageY - target.offsetTop;
+			}
+		}
+		// clear form vars
+		setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
+	});
+};
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+	return this.unbind('submit.form-plugin click.form-plugin');
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property.  An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic) {
+	var a = [];
+	if (this.length === 0) {
+		return a;
+	}
+
+	var form = this[0];
+	var els = semantic ? form.getElementsByTagName('*') : form.elements;
+	if (!els) {
+		return a;
+	}
+	
+	var i,j,n,v,el,max,jmax;
+	for(i=0, max=els.length; i < max; i++) {
+		el = els[i];
+		n = el.name;
+		if (!n) {
+			continue;
+		}
+
+		if (semantic && form.clk && el.type == "image") {
+			// handle image inputs on the fly when semantic == true
+			if(!el.disabled && form.clk == el) {
+				a.push({name: n, value: $(el).val()});
+				a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+			}
+			continue;
+		}
+
+		v = $.fieldValue(el, true);
+		if (v && v.constructor == Array) {
+			for(j=0, jmax=v.length; j < jmax; j++) {
+				a.push({name: n, value: v[j]});
+			}
+		}
+		else if (v !== null && typeof v != 'undefined') {
+			a.push({name: n, value: v});
+		}
+	}
+
+	if (!semantic && form.clk) {
+		// input type=='image' are not found in elements array! handle it here
+		var $input = $(form.clk), input = $input[0];
+		n = input.name;
+		if (n && !input.disabled && input.type == 'image') {
+			a.push({name: n, value: $input.val()});
+			a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+		}
+	}
+	return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&amp;name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+	//hand off to jQuery.param for proper encoding
+	return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&amp;name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+	var a = [];
+	this.each(function() {
+		var n = this.name;
+		if (!n) {
+			return;
+		}
+		var v = $.fieldValue(this, successful);
+		if (v && v.constructor == Array) {
+			for (var i=0,max=v.length; i < max; i++) {
+				a.push({name: n, value: v[i]});
+			}
+		}
+		else if (v !== null && typeof v != 'undefined') {
+			a.push({name: this.name, value: v});
+		}
+	});
+	//hand off to jQuery.param for proper encoding
+	return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set.  For example, consider the following form:
+ *
+ *  <form><fieldset>
+ *	  <input name="A" type="text" />
+ *	  <input name="A" type="text" />
+ *	  <input name="B" type="checkbox" value="B1" />
+ *	  <input name="B" type="checkbox" value="B2"/>
+ *	  <input name="C" type="radio" value="C1" />
+ *	  <input name="C" type="radio" value="C2" />
+ *  </fieldset></form>
+ *
+ *  var v = $(':text').fieldValue();
+ *  // if no values are entered into the text inputs
+ *  v == ['','']
+ *  // if values entered into the text inputs are 'foo' and 'bar'
+ *  v == ['foo','bar']
+ *
+ *  var v = $(':checkbox').fieldValue();
+ *  // if neither checkbox is checked
+ *  v === undefined
+ *  // if both checkboxes are checked
+ *  v == ['B1', 'B2']
+ *
+ *  var v = $(':radio').fieldValue();
+ *  // if neither radio is checked
+ *  v === undefined
+ *  // if first radio is checked
+ *  v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true.  If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array.  If no valid value can be determined the
+ *	   array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+	for (var val=[], i=0, max=this.length; i < max; i++) {
+		var el = this[i];
+		var v = $.fieldValue(el, successful);
+		if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
+			continue;
+		}
+		v.constructor == Array ? $.merge(val, v) : val.push(v);
+	}
+	return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+	var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+	if (successful === undefined) {
+		successful = true;
+	}
+
+	if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+		(t == 'checkbox' || t == 'radio') && !el.checked ||
+		(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+		tag == 'select' && el.selectedIndex == -1)) {
+			return null;
+	}
+
+	if (tag == 'select') {
+		var index = el.selectedIndex;
+		if (index < 0) {
+			return null;
+		}
+		var a = [], ops = el.options;
+		var one = (t == 'select-one');
+		var max = (one ? index+1 : ops.length);
+		for(var i=(one ? index : 0); i < max; i++) {
+			var op = ops[i];
+			if (op.selected) {
+				var v = op.value;
+				if (!v) { // extra pain for IE...
+					v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
+				}
+				if (one) {
+					return v;
+				}
+				a.push(v);
+			}
+		}
+		return a;
+	}
+	return $(el).val();
+};
+
+/**
+ * Clears the form data.  Takes the following actions on the form's input fields:
+ *  - input text fields will have their 'value' property set to the empty string
+ *  - select elements will have their 'selectedIndex' property set to -1
+ *  - checkbox and radio inputs will have their 'checked' property set to false
+ *  - inputs of type submit, button, reset, and hidden will *not* be effected
+ *  - button elements will *not* be effected
+ */
+$.fn.clearForm = function() {
+	return this.each(function() {
+		$('input,select,textarea', this).clearFields();
+	});
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+	return this.each(function() {
+		var t = this.type, tag = this.tagName.toLowerCase();
+		if (t == 'text' || t == 'password' || tag == 'textarea') {
+			this.value = '';
+		}
+		else if (t == 'checkbox' || t == 'radio') {
+			this.checked = false;
+		}
+		else if (tag == 'select') {
+			this.selectedIndex = -1;
+		}
+	});
+};
+
+/**
+ * Resets the form data.  Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+	return this.each(function() {
+		// guard against an input with the name of 'reset'
+		// note that IE reports the reset function as an 'object'
+		if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
+			this.reset();
+		}
+	});
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+	if (b === undefined) {
+		b = true;
+	}
+	return this.each(function() {
+		this.disabled = !b;
+	});
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+	if (select === undefined) {
+		select = true;
+	}
+	return this.each(function() {
+		var t = this.type;
+		if (t == 'checkbox' || t == 'radio') {
+			this.checked = select;
+		}
+		else if (this.tagName.toLowerCase() == 'option') {
+			var $sel = $(this).parent('select');
+			if (select && $sel[0] && $sel[0].type == 'select-one') {
+				// deselect all other options
+				$sel.find('option').selected(false);
+			}
+			this.selected = select;
+		}
+	});
+};
+
+// helper fn for console logging
+// set $.fn.ajaxSubmit.debug to true to enable debug logging
+function log() {
+	if ($.fn.ajaxSubmit.debug) {
+		var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
+		if (window.console && window.console.log) {
+			window.console.log(msg);
+		}
+		else if (window.opera && window.opera.postError) {
+			window.opera.postError(msg);
+		}
+	}
+};
+
+})(jQuery);
diff --git a/init.solaris11 b/init.solaris11
new file mode 100644
index 0000000000000000000000000000000000000000..9c512a3dc1f3926e13be24ed65911a16984bd02e
--- /dev/null
+++ b/init.solaris11
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+<!--
+  Assumes user=sickbeard group=other
+  Assumes /opt/sickbeard is installation directory
+ 
+  See http://www.sun.com/bigadmin/content/selfheal/sdev_intro.jsp for more information
+ 
+  To install (see http://docs.sun.com/app/docs/doc/819-2379/fgour?l=en&a=view for more information)
+    svccfg import sickbeard.smf
+ 
+  To enable/disable
+    svcadm enable sickbeard
+    svcadm disable sickbeard
+ 
+  To check if failures
+    svcs -xv
+ 
+  To check logs
+    tail /var/svc/log/network-sickbeard\:default.log
+-->
+ 
+<service_bundle type='manifest' name='sickbeard'>
+ 
+  <service
+    name='network/sickbeard'
+    type='service'
+    version='1'>
+ 
+    <create_default_instance enabled='false' />
+    <single_instance />
+ 
+   <!--
+      Only start in muti-user mode
+   -->
+   <dependency name='multi-user'
+      grouping='require_all'
+      restart_on='none'
+      type='service'>
+      <service_fmri value='svc:/milestone/multi-user' />
+   </dependency>
+ 
+    <!--
+       Wait for network interfaces to be initialized.
+    -->
+    <dependency name='network'
+        grouping='require_all'
+        restart_on='none'
+        type='service'>
+        <service_fmri value='svc:/milestone/network:default'/>
+    </dependency>
+ 
+    <!--
+       Wait for all local filesystems to be mounted.
+    -->
+    <dependency name='filesystem-local'
+        grouping='require_all'
+        restart_on='none'
+        type='service'>
+        <service_fmri value='svc:/system/filesystem/local:default'/>
+    </dependency>
+ 
+    <!-- execute as user sickbeard -->
+    <method_context>
+        <method_credential user='sickbeard' group='other' />
+    </method_context>
+ 
+    <exec_method
+        type='method'
+        name='start'
+        exec='/opt/sickbeard/SickBeard.py --daemon'
+        timeout_seconds='60'>
+    </exec_method>
+ 
+    <exec_method
+        type='method'
+        name='stop'
+        exec=':kill'
+        timeout_seconds='2'>
+    </exec_method>
+ 
+    <template>
+       <common_name>
+          <loctext xml:lang='C'>Sickbeard</loctext>
+       </common_name>
+       <documentation>
+          <doc_link name='sickbeard'
+                    uri='http://www.sickbeard.com/' />
+       </documentation>
+    </template>
+ 
+  </service>
+ 
+</service_bundle>
+
diff --git a/lib/httplib2/__init__.py b/lib/httplib2/__init__.py
index 39b78f4d1eeae608d99602ac2c33583990eefa21..01151f7fa257f7747fb2ecab6d27035533d68426 100644
--- a/lib/httplib2/__init__.py
+++ b/lib/httplib2/__init__.py
@@ -660,7 +660,7 @@ class FileCache(object):
             f = file(cacheFullPath, "rb")
             retval = f.read()
             f.close()
-        except IOError:
+        except IOError, e:
             pass
         return retval
 
@@ -1048,7 +1048,7 @@ a string that contains the response entity body.
                         feedparser.feed(info)
                         info = feedparser.close()
                         feedparser._parse = None
-                    except IndexError:
+                    except IndexError, ValueError:
                         self.cache.delete(cachekey)
                         cachekey = None
                         cached_value = None
@@ -1147,7 +1147,7 @@ a string that contains the response entity body.
                     content = e.content
                     response.status = 500
                     response.reason = str(e) 
-                elif isinstance(e, socket.timeout):
+                elif isinstance(e, socket.timeout) or (isinstance(e, socket.error) and 'timed out' in str(e)):
                     content = "Request Timeout"
                     response = Response( {
                             "content-type": "text/plain",
diff --git a/lib/tvdb_api/tvdb_api.py b/lib/tvdb_api/tvdb_api.py
index 00902c91dca3a130fab529c467c2856cd204595a..fa3ece36d08cd5cd03743369afd4f6a2af438632 100644
--- a/lib/tvdb_api/tvdb_api.py
+++ b/lib/tvdb_api/tvdb_api.py
@@ -29,6 +29,7 @@ import logging
 import datetime
 import time
 import traceback
+import socket
 
 try:
     import xml.etree.cElementTree as ElementTree
@@ -519,7 +520,7 @@ class Tvdb:
         try:
             log().debug("Retrieving URL %s" % url)
             header, resp = h.request(url, headers=h_header)
-        except (IOError, httplib2.HttpLib2Error), errormsg:
+        except (socket.error, IOError, httplib2.HttpLib2Error), errormsg:
             if not str(errormsg).startswith('HTTP Error'):
                 lastTimeout = datetime.datetime.now()
             raise tvdb_error("Could not connect to server %s: %s" % (url, errormsg))
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index 930c36b0f6928a3e06df6ec7867638803b235f3d..dc2dcf4b7bcfd9c5aaa8b24275b195064f804cd2 100644
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -30,7 +30,7 @@ from threading import Lock
 
 # apparently py2exe won't build these unless they're imported somewhere
 from sickbeard import providers, metadata
-from providers import ezrss, tvtorrents, nzbs_org, nzbmatrix, tvbinz, nzbsrus, newznab, womble, newzbin
+from providers import ezrss, tvtorrents, nzbs_org, nzbmatrix, nzbsrus, newznab, womble, newzbin
 
 from sickbeard import searchCurrent, searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser
 from sickbeard import helpers, db, exceptions, show_queue, search_queue, scheduler
@@ -56,6 +56,9 @@ MY_FULLNAME = None
 MY_NAME = None
 MY_ARGS = []
 SYS_ENCODING = ''
+DATA_DIR = ''
+CREATEPID = False
+PIDFILE = ''
 
 DAEMON = None
 
@@ -95,6 +98,7 @@ WEB_IPV6 = None
 
 LAUNCH_BROWSER = None
 CACHE_DIR = None
+ACTUAL_CACHE_DIR = None
 ROOT_DIRS = None
 
 USE_BANNER = None
@@ -152,12 +156,6 @@ KEEP_PROCESSED_DIR = False
 MOVE_ASSOCIATED_FILES = False
 TV_DOWNLOAD_DIR = None
 
-SHOW_TVBINZ = False
-TVBINZ = False
-TVBINZ_UID = None
-TVBINZ_HASH = None
-TVBINZ_AUTH = None
-
 NZBS = False
 NZBS_UID = None
 NZBS_HASH = None
@@ -180,7 +178,7 @@ SAB_USERNAME = None
 SAB_PASSWORD = None
 SAB_APIKEY = None
 SAB_CATEGORY = None
-SAB_HOST = None
+SAB_HOST = ''
 
 NZBGET_PASSWORD = None
 NZBGET_CATEGORY = None
@@ -191,7 +189,7 @@ XBMC_NOTIFY_ONSNATCH = False
 XBMC_NOTIFY_ONDOWNLOAD = False
 XBMC_UPDATE_LIBRARY = False
 XBMC_UPDATE_FULL = False
-XBMC_HOST = None
+XBMC_HOST = ''
 XBMC_USERNAME = None
 XBMC_PASSWORD = None
 
@@ -207,7 +205,7 @@ PLEX_PASSWORD = None
 USE_GROWL = False
 GROWL_NOTIFY_ONSNATCH = False
 GROWL_NOTIFY_ONDOWNLOAD = False
-GROWL_HOST = None
+GROWL_HOST = ''
 GROWL_PASSWORD = None
 
 USE_PROWL = False
@@ -239,7 +237,6 @@ NMJ_HOST = None
 NMJ_DATABASE = None
 NMJ_MOUNT = None
 
-
 COMING_EPS_LAYOUT = None
 COMING_EPS_DISPLAY_PAUSED = None
 COMING_EPS_SORT = None
@@ -248,6 +245,8 @@ EXTRA_SCRIPTS = []
 
 GIT_PATH = None
 
+IGNORE_WORDS = "german,french,core2hd,dutch,swedish"
+
 __INITIALIZED__ = False
 
 def CheckSection(sec):
@@ -329,7 +328,7 @@ def check_setting_str(config, cfg_name, item_name, def_val, log=True):
 
 
 def get_backlog_cycle_time():
-    cycletime = sickbeard.SEARCH_FREQUENCY*2+7
+    cycletime = SEARCH_FREQUENCY*2+7
     return max([cycletime, 720])
 
 
@@ -338,7 +337,7 @@ def initialize(consoleLogging=True):
     with INIT_LOCK:
 
         global LOG_DIR, WEB_PORT, WEB_LOG, WEB_ROOT, WEB_USERNAME, WEB_PASSWORD, WEB_HOST, WEB_IPV6, \
-                USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, TVBINZ, TVBINZ_UID, TVBINZ_HASH, DOWNLOAD_PROPERS, \
+                USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, \
                 SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_HOST, \
                 NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, currentSearchScheduler, backlogSearchScheduler, \
                 USE_XBMC, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_UPDATE_FULL, \
@@ -353,8 +352,8 @@ def initialize(consoleLogging=True):
                 USE_GROWL, GROWL_HOST, GROWL_PASSWORD, USE_PROWL, PROWL_NOTIFY_ONSNATCH, PROWL_NOTIFY_ONDOWNLOAD, PROWL_API, PROWL_PRIORITY, PROG_DIR, NZBMATRIX, NZBMATRIX_USERNAME, \
                 NZBMATRIX_APIKEY, versionCheckScheduler, VERSION_NOTIFY, PROCESS_AUTOMATICALLY, \
                 KEEP_PROCESSED_DIR, TV_DOWNLOAD_DIR, TVDB_BASE_URL, MIN_SEARCH_FREQUENCY, \
-                TVBINZ_AUTH, showQueueScheduler, searchQueueScheduler, ROOT_DIRS, \
-                NAMING_SHOW_NAME, NAMING_EP_TYPE, NAMING_MULTI_EP_TYPE, CACHE_DIR, TVDB_API_PARMS, \
+                showQueueScheduler, searchQueueScheduler, ROOT_DIRS, \
+                NAMING_SHOW_NAME, NAMING_EP_TYPE, NAMING_MULTI_EP_TYPE, CACHE_DIR, ACTUAL_CACHE_DIR, TVDB_API_PARMS, \
                 RENAME_EPISODES, properFinderScheduler, PROVIDER_ORDER, autoPostProcesserScheduler, \
                 NAMING_EP_NAME, NAMING_SEP_TYPE, NAMING_USE_PERIODS, WOMBLE, \
                 NZBSRUS, NZBSRUS_UID, NZBSRUS_HASH, NAMING_QUALITY, providerList, newznabProviderList, \
@@ -363,7 +362,7 @@ def initialize(consoleLogging=True):
                 USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, \
                 USE_BANNER, USE_LISTVIEW, METADATA_XBMC, METADATA_MEDIABROWSER, METADATA_PS3, metadata_provider_dict, \
                 NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, \
-                COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, METADATA_WDTV
+                COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, METADATA_WDTV, IGNORE_WORDS
 
         if __INITIALIZED__:
             return False
@@ -373,7 +372,6 @@ def initialize(consoleLogging=True):
         CheckSection('General')
         CheckSection('Blackhole')
         CheckSection('Newzbin')
-        CheckSection('TVBinz')
         CheckSection('SABnzbd')
         CheckSection('NZBget')
         CheckSection('XBMC')
@@ -403,10 +401,17 @@ def initialize(consoleLogging=True):
         WEB_PASSWORD = check_setting_str(CFG, 'General', 'web_password', '')
         LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1))
 
-        CACHE_DIR = check_setting_str(CFG, 'General', 'cache_dir', 'cache')
+        ACTUAL_CACHE_DIR = check_setting_str(CFG, 'General', 'cache_dir', 'cache')
         # fix bad configs due to buggy code
-        if CACHE_DIR == 'None':
-            CACHE_DIR = 'cache'
+        if ACTUAL_CACHE_DIR == 'None':
+            ACTUAL_CACHE_DIR = 'cache'
+        
+        # unless they specify, put the cache dir inside the data dir
+        if not os.path.isabs(ACTUAL_CACHE_DIR):
+            CACHE_DIR = os.path.join(DATA_DIR, ACTUAL_CACHE_DIR)
+        else:
+            CACHE_DIR = ACTUAL_CACHE_DIR
+        
         if not helpers.makeDir(CACHE_DIR):
             logger.log(u"!!! Creating local cache dir failed, using system default", logger.ERROR)
             CACHE_DIR = None
@@ -483,11 +488,6 @@ def initialize(consoleLogging=True):
         TVTORRENTS_DIGEST = check_setting_str(CFG, 'TVTORRENTS', 'tvtorrents_digest', '')
         TVTORRENTS_HASH = check_setting_str(CFG, 'TVTORRENTS', 'tvtorrents_hash', '')
 
-        TVBINZ = bool(check_setting_int(CFG, 'TVBinz', 'tvbinz', 0))
-        TVBINZ_UID = check_setting_str(CFG, 'TVBinz', 'tvbinz_uid', '')
-        TVBINZ_HASH = check_setting_str(CFG, 'TVBinz', 'tvbinz_hash', '')
-        TVBINZ_AUTH = check_setting_str(CFG, 'TVBinz', 'tvbinz_auth', '')
-
         NZBS = bool(check_setting_int(CFG, 'NZBs', 'nzbs', 0))
         NZBS_UID = check_setting_str(CFG, 'NZBs', 'nzbs_uid', '')
         NZBS_HASH = check_setting_str(CFG, 'NZBs', 'nzbs_hash', '')
@@ -571,6 +571,8 @@ def initialize(consoleLogging=True):
 
         GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '')
 
+        IGNORE_WORDS = check_setting_str(CFG, 'General', 'ignore_words', IGNORE_WORDS)
+
         EXTRA_SCRIPTS = [x for x in check_setting_str(CFG, 'General', 'extra_scripts', '').split('|') if x]
 
         USE_BANNER = bool(check_setting_int(CFG, 'General', 'use_banner', 0))
@@ -637,7 +639,7 @@ def initialize(consoleLogging=True):
 
         providerList = providers.makeProviderList()
         
-        logger.initLogging(consoleLogging=consoleLogging)
+        logger.sb_log_instance.initLogging(consoleLogging=consoleLogging)
 
         # initialize the main SB database
         db.upgradeDatabase(db.DBConnection(), mainDB.InitialSchema)
@@ -653,12 +655,6 @@ def initialize(consoleLogging=True):
                                                      threadName="SEARCH",
                                                      runImmediately=True)
 
-        backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(),
-                                                                      cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()),
-                                                                      threadName="BACKLOG",
-                                                                      runImmediately=False)
-        backlogSearchScheduler.action.cycleTime = BACKLOG_SEARCH_FREQUENCY
-
         # the interval for this is stored inside the ShowUpdater class
         showUpdaterInstance = showUpdater.ShowUpdater()
         showUpdateScheduler = scheduler.Scheduler(showUpdaterInstance,
@@ -692,6 +688,12 @@ def initialize(consoleLogging=True):
                                                      threadName="POSTPROCESSER",
                                                      runImmediately=True)
 
+        backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(),
+                                                                      cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()),
+                                                                      threadName="BACKLOG",
+                                                                      runImmediately=True)
+        backlogSearchScheduler.action.cycleTime = BACKLOG_SEARCH_FREQUENCY
+
 
         showList = []
         loadingShowList = {}
@@ -839,27 +841,29 @@ def saveAndShutdown(restart=False):
     logger.log(u"Killing cherrypy")
     cherrypy.engine.exit()
 
-    if sickbeard.CREATEPID:
-        logger.log(u"Removing pidfile " + str(sickbeard.PIDFILE))
-        os.remove(sickbeard.PIDFILE)
+    if CREATEPID:
+        logger.log(u"Removing pidfile " + str(PIDFILE))
+        os.remove(PIDFILE)
 
     if restart:
-        install_type = sickbeard.versionCheckScheduler.action.install_type
+        install_type = versionCheckScheduler.action.install_type
 
         popen_list = []
 
         if install_type in ('git', 'source'):
-            popen_list = [sys.executable, sickbeard.MY_FULLNAME]
+            popen_list = [sys.executable, 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]
+                popen_list = [os.path.join(PROG_DIR, 'updater.exe'), str(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 ]
+                popen_list = [sys.executable, os.path.join(PROG_DIR, 'updater.py'), str(PID), sys.executable, MY_FULLNAME ]
 
         if popen_list:
-            popen_list += sickbeard.MY_ARGS
+            popen_list += MY_ARGS
+            if '--nolaunch' not in popen_list:
+                popen_list += ['--nolaunch']
             logger.log(u"Restarting Sick Beard with " + str(popen_list))
             subprocess.Popen(popen_list, cwd=os.getcwd())
 
@@ -867,16 +871,17 @@ def saveAndShutdown(restart=False):
 
 
 def invoke_command(to_call, *args, **kwargs):
+    global invoked_command
     def delegate():
         to_call(*args, **kwargs)
-    sickbeard.invoked_command = delegate
-    logger.log(u"Placed invoked command: "+repr(sickbeard.invoked_command)+" for "+repr(to_call)+" with "+repr(args)+" and "+repr(kwargs), logger.DEBUG)
+    invoked_command = delegate
+    logger.log(u"Placed invoked command: "+repr(invoked_command)+" for "+repr(to_call)+" with "+repr(args)+" and "+repr(kwargs), logger.DEBUG)
 
 def invoke_restart(soft=True):
-    invoke_command(sickbeard.restart, soft=soft)
+    invoke_command(restart, soft=soft)
 
 def invoke_shutdown():
-    invoke_command(sickbeard.saveAndShutdown)
+    invoke_command(saveAndShutdown)
 
 
 def restart(soft=True):
@@ -897,7 +902,7 @@ def restart(soft=True):
 def save_config():
 
     new_config = ConfigObj()
-    new_config.filename = sickbeard.CONFIG_FILE
+    new_config.filename = CONFIG_FILE
 
     new_config['General'] = {}
     new_config['General']['log_dir'] = LOG_DIR
@@ -937,7 +942,7 @@ def save_config():
     new_config['General']['metadata_ps3'] = metadata_provider_dict['Sony PS3'].get_config()
     new_config['General']['metadata_wdtv'] = metadata_provider_dict['WDTV'].get_config()
 
-    new_config['General']['cache_dir'] = CACHE_DIR if CACHE_DIR else 'cache'
+    new_config['General']['cache_dir'] = ACTUAL_CACHE_DIR if ACTUAL_CACHE_DIR else 'cache'
     new_config['General']['root_dirs'] = ROOT_DIRS if ROOT_DIRS else ''
     new_config['General']['tv_download_dir'] = TV_DOWNLOAD_DIR
     new_config['General']['keep_processed_dir'] = int(KEEP_PROCESSED_DIR)
@@ -947,6 +952,7 @@ def save_config():
     
     new_config['General']['extra_scripts'] = '|'.join(EXTRA_SCRIPTS)
     new_config['General']['git_path'] = GIT_PATH
+    new_config['General']['ignore_words'] = IGNORE_WORDS
 
     new_config['Blackhole'] = {}
     new_config['Blackhole']['nzb_dir'] = NZB_DIR
@@ -960,12 +966,6 @@ def save_config():
     new_config['TVTORRENTS']['tvtorrents_digest'] = TVTORRENTS_DIGEST
     new_config['TVTORRENTS']['tvtorrents_hash'] = TVTORRENTS_HASH
 
-    new_config['TVBinz'] = {}
-    new_config['TVBinz']['tvbinz'] = int(TVBINZ)
-    new_config['TVBinz']['tvbinz_uid'] = TVBINZ_UID
-    new_config['TVBinz']['tvbinz_hash'] = TVBINZ_HASH
-    new_config['TVBinz']['tvbinz_auth'] = TVBINZ_AUTH
-
     new_config['NZBs'] = {}
     new_config['NZBs']['nzbs'] = int(NZBS)
     new_config['NZBs']['nzbs_uid'] = NZBS_UID
@@ -1089,7 +1089,7 @@ def getEpList(epIDs, showid=None):
     if epIDs == None or len(epIDs) == 0:
         return []
 
-    query = "SELECT * FROM tv_episodes WHERE tvdbid in (%s)" % (",".join(["?" for x in epIDs]),)
+    query = "SELECT * FROM tv_episodes WHERE tvdbid in (%s)" % (",".join(['?']*len(epIDs)),)
     params = epIDs
 
     if showid != None:
@@ -1102,7 +1102,7 @@ def getEpList(epIDs, showid=None):
     epList = []
 
     for curEp in sqlResults:
-        curShowObj = helpers.findCertainShow(sickbeard.showList, int(curEp["showid"]))
+        curShowObj = helpers.findCertainShow(showList, int(curEp["showid"]))
         curEpObj = curShowObj.getEpisode(int(curEp["season"]), int(curEp["episode"]))
         epList.append(curEpObj)
 
diff --git a/sickbeard/autoPostProcesser.py b/sickbeard/autoPostProcesser.py
index 0bcee76de8c1d8bab7c24f7828b9dbb6d1254869..4bfc35575060916cebda6f5fb6af59141d075844 100644
--- a/sickbeard/autoPostProcesser.py
+++ b/sickbeard/autoPostProcesser.py
@@ -16,16 +16,13 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
+import os.path
+
 import sickbeard
-from sickbeard import helpers
-from sickbeard import logger
 
+from sickbeard import logger
 from sickbeard import encodingKludge as ek
 from sickbeard import processTV
-from sickbeard import db
-
-import os.path
-
 
 class PostProcesser():
 
@@ -41,9 +38,4 @@ class PostProcesser():
             logger.log(u"Automatic post-processing attempted but dir "+sickbeard.TV_DOWNLOAD_DIR+" is relative (and probably not what you really want to process)", logger.ERROR)
             return
 
-        myDB = db.DBConnection()
-        sqlResults = myDB.select("SELECT * FROM tv_shows WHERE location = ? OR location LIKE ?",
-                                 [os.path.abspath(sickbeard.TV_DOWNLOAD_DIR),
-                                  ek.ek(os.path.join, os.path.abspath(sickbeard.TV_DOWNLOAD_DIR), '%')])
-
         processTV.processDir(sickbeard.TV_DOWNLOAD_DIR)
diff --git a/sickbeard/browser.py b/sickbeard/browser.py
index 69f89e4e13383ea9a27b3eb20b8095ce6955ab8b..c89728e137ae992b31a6f27b581f7d7e88056202 100644
--- a/sickbeard/browser.py
+++ b/sickbeard/browser.py
@@ -17,7 +17,6 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
-import glob
 import string
 import cherrypy
 
@@ -38,7 +37,7 @@ def getWinDrives():
     assert os.name == 'nt'
 
     drives = []
-    bitmask = windll.kernel32.GetLogicalDrives()
+    bitmask = windll.kernel32.GetLogicalDrives() #@UndefinedVariable
     for letter in string.uppercase:
         if bitmask & 1:
             drives.append(letter)
diff --git a/sickbeard/classes.py b/sickbeard/classes.py
index 3c2cf32acf699003d3896463e287c333727ba2ae..3a50950e6b3cc03d28ea0a076f8718ce7577e860 100644
--- a/sickbeard/classes.py
+++ b/sickbeard/classes.py
@@ -23,9 +23,7 @@ import sickbeard
 import urllib
 import datetime
 
-from common import *
-
-from sickbeard import providers
+from common import USER_AGENT
 
 class SickBeardURLopener(urllib.FancyURLopener):
     version = USER_AGENT
diff --git a/sickbeard/common.py b/sickbeard/common.py
index 9692dadd78cbaeae361505294a77352807d32660..f6d69abb733a1fb8e4674188f277dc46e0c2f08d 100644
--- a/sickbeard/common.py
+++ b/sickbeard/common.py
@@ -16,7 +16,6 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import sickbeard
 import os.path
 import operator, platform
 import re
diff --git a/sickbeard/config.py b/sickbeard/config.py
index f67a3fa15af1dd41a8342e446483ee5827ce8918..e7683b5861c7b10c2f6af8001b05d9f288a77add 100644
--- a/sickbeard/config.py
+++ b/sickbeard/config.py
@@ -46,7 +46,7 @@ def change_LOG_DIR(log_dir):
     if os.path.normpath(sickbeard.LOG_DIR) != os.path.normpath(log_dir):
         if helpers.makeDir(log_dir):
             sickbeard.LOG_DIR = os.path.normpath(log_dir)
-            logger.initLogging()
+            logger.sb_log_instance.initLogging()
             logger.log(u"Initialized new log file in " + log_dir)
 
             cherry_log = os.path.join(sickbeard.LOG_DIR, "cherrypy.log")
@@ -132,5 +132,5 @@ def change_VERSION_NOTIFY(version_notify):
         sickbeard.NEWEST_VERSION_STRING = None;
         
     if oldSetting == False and version_notify == True:
-        sickbeard.versionCheckScheduler.action.run()
+        sickbeard.versionCheckScheduler.action.run() #@UndefinedVariable
 
diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py
index 2acb6fe20b36a67115ed9627f56baf8430219166..e78e8748f9d89c2fb7f9d5ae2b3098553c9f9440 100644
--- a/sickbeard/databases/mainDB.py
+++ b/sickbeard/databases/mainDB.py
@@ -25,8 +25,7 @@ from sickbeard import logger
 from sickbeard.providers.generic import GenericProvider
 
 from sickbeard import encodingKludge as ek
-
-
+from sickbeard.exceptions import ex
 
 class MainSanityCheck(db.DBSanityCheck):
 
@@ -131,17 +130,17 @@ class NewQualitySettings (NumericProviders):
     def execute(self):
 
         numTries = 0
-        while not ek.ek(os.path.isfile, ek.ek(os.path.join, sickbeard.PROG_DIR, 'sickbeard.db.v0')):
-            if not ek.ek(os.path.isfile, ek.ek(os.path.join, sickbeard.PROG_DIR, 'sickbeard.db')):
+        while not ek.ek(os.path.isfile, db.dbFilename(suffix='v0')):
+            if not ek.ek(os.path.isfile, db.dbFilename()):
                 break
 
             try:
                 logger.log(u"Attempting to back up your sickbeard.db file before migration...")
-                shutil.copy(ek.ek(os.path.join, sickbeard.PROG_DIR, 'sickbeard.db'), ek.ek(os.path.join, sickbeard.PROG_DIR, 'sickbeard.db.v0'))
+                shutil.copy(db.dbFilename(), db.dbFilename(suffix='v0'))
                 logger.log(u"Done backup, proceeding with migration.")
                 break
             except Exception, e:
-                logger.log(u"Error while trying to back up your sickbeard.db: "+str(e).decode('utf-8'))
+                logger.log(u"Error while trying to back up your sickbeard.db: "+ex(e))
                 numTries += 1
                 time.sleep(1)
                 logger.log(u"Trying again.")
@@ -212,7 +211,7 @@ class NewQualitySettings (NumericProviders):
 
         # if no updates were done then the backup is useless
         if didUpdate:
-            os.remove(ek.ek(os.path.join, sickbeard.PROG_DIR, 'sickbeard.db.v0'))
+            os.remove(db.dbFilename(suffix='v0'))
 
 
         ### Update show qualities
@@ -398,4 +397,4 @@ class FixAirByDateSetting(SetNzbTorrentSettings):
             if cur_show["genre"] and "talk show" in cur_show["genre"].lower():
                 self.connection.action("UPDATE tv_shows SET air_by_date = ? WHERE tvdb_id = ?", [1, cur_show["tvdb_id"]])
         
-        self.incDBVersion()
\ No newline at end of file
+        self.incDBVersion()
diff --git a/sickbeard/db.py b/sickbeard/db.py
index 905617e2fc672df76489a803e584a65d6639cf57..5e63b28a2f980bcf45ef474cd85891d7580e7262 100644
--- a/sickbeard/db.py
+++ b/sickbeard/db.py
@@ -26,147 +26,160 @@ import threading
 
 import sickbeard
 
+from sickbeard import encodingKludge as ek
 from sickbeard import logger
+from sickbeard.exceptions import ex
 
 db_lock = threading.Lock()
 
-class DBConnection:
-	def __init__(self, dbFileName="sickbeard.db"):
-
-		self.dbFileName = dbFileName
-
-		self.connection = sqlite3.connect(os.path.join(sickbeard.PROG_DIR, self.dbFileName), 20)
-		self.connection.row_factory = sqlite3.Row
-
- 	def action(self, query, args=None):
-
- 		with db_lock:
-
-			if query == None:
-				return
-	
-			sqlResult = None
-			attempt = 0
-	
-			while attempt < 5:
-				try:
-					if args == None:
-						logger.log(self.dbFileName+": "+query, logger.DEBUG)
-						sqlResult = self.connection.execute(query)
-					else:
-						logger.log(self.dbFileName+": "+query+" with args "+str(args), logger.DEBUG)
-						sqlResult = self.connection.execute(query, args)
-					self.connection.commit()
-					# get out of the connection attempt loop since we were successful
-					break
-				except sqlite3.OperationalError, e:
-					if "unable to open database file" in str(e) or "database is locked" in str(e):
-						logger.log(u"DB error: "+str(e).decode('utf-8'), logger.WARNING)
-						attempt += 1
-						time.sleep(1)
-					else:
-						logger.log(u"DB error: "+str(e).decode('utf-8'), logger.ERROR)
-						raise
-				except sqlite3.DatabaseError, e:
-					logger.log(u"Fatal error executing query: " + str(e), logger.ERROR)
-					raise
-	
-			return sqlResult
-
-
-	def select(self, query, args=None):
-
-		sqlResults = self.action(query, args).fetchall()
+def dbFilename(filename="sickbeard.db", suffix=None):
+    """
+    @param filename: The sqlite database filename to use. If not specified,
+                     will be made to be sickbeard.db
+    @param suffix: The suffix to append to the filename. A '.' will be added
+                   automatically, i.e. suffix='v0' will make dbfile.db.v0
+    @return: the correct location of the database file.
+    """
+    if suffix:
+        filename = "%s.%s" % (filename, suffix)
+    return ek.ek(os.path.join, sickbeard.DATA_DIR, filename)
 
-		if sqlResults == None:
-			return []
-
-		return sqlResults
-
-	def upsert(self, tableName, valueDict, keyDict):
-
-		changesBefore = self.connection.total_changes
-
-		genParams = lambda myDict : [x + " = ?" for x in myDict.keys()]
-
-		query = "UPDATE "+tableName+" SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict))
-
-		self.action(query, valueDict.values() + keyDict.values())
-
-		if self.connection.total_changes == changesBefore:
-			query = "INSERT INTO "+tableName+" (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \
-			         " VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")"
-			self.action(query, valueDict.values() + keyDict.values())
-
-	def tableInfo(self, tableName):
-		# FIXME ? binding is not supported here, but I cannot find a way to escape a string manually
-		cursor = self.connection.execute("PRAGMA table_info(%s)" % tableName)
-		columns = {}
-		for column in cursor:
-			columns[column['name']] = { 'type': column['type'] }
-		return columns
+class DBConnection:
+    def __init__(self, filename="sickbeard.db", suffix=None):
+
+        self.filename = filename
+        self.connection = sqlite3.connect(dbFilename(filename), 20)
+        self.connection.row_factory = sqlite3.Row
+
+    def action(self, query, args=None):
+
+        with db_lock:
+
+            if query == None:
+                return
+    
+            sqlResult = None
+            attempt = 0
+    
+            while attempt < 5:
+                try:
+                    if args == None:
+                        logger.log(self.filename+": "+query, logger.DEBUG)
+                        sqlResult = self.connection.execute(query)
+                    else:
+                        logger.log(self.filename+": "+query+" with args "+str(args), logger.DEBUG)
+                        sqlResult = self.connection.execute(query, args)
+                    self.connection.commit()
+                    # get out of the connection attempt loop since we were successful
+                    break
+                except sqlite3.OperationalError, e:
+                    if "unable to open database file" in e.message or "database is locked" in e.message:
+                        logger.log(u"DB error: "+ex(e), logger.WARNING)
+                        attempt += 1
+                        time.sleep(1)
+                    else:
+                        logger.log(u"DB error: "+ex(e), logger.ERROR)
+                        raise
+                except sqlite3.DatabaseError, e:
+                    logger.log(u"Fatal error executing query: " + ex(e), logger.ERROR)
+                    raise
+    
+            return sqlResult
+
+
+    def select(self, query, args=None):
+
+        sqlResults = self.action(query, args).fetchall()
+
+        if sqlResults == None:
+            return []
+
+        return sqlResults
+
+    def upsert(self, tableName, valueDict, keyDict):
+
+        changesBefore = self.connection.total_changes
+
+        genParams = lambda myDict : [x + " = ?" for x in myDict.keys()]
+
+        query = "UPDATE "+tableName+" SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict))
+
+        self.action(query, valueDict.values() + keyDict.values())
+
+        if self.connection.total_changes == changesBefore:
+            query = "INSERT INTO "+tableName+" (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \
+                     " VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")"
+            self.action(query, valueDict.values() + keyDict.values())
+
+    def tableInfo(self, tableName):
+        # FIXME ? binding is not supported here, but I cannot find a way to escape a string manually
+        cursor = self.connection.execute("PRAGMA table_info(%s)" % tableName)
+        columns = {}
+        for column in cursor:
+            columns[column['name']] = { 'type': column['type'] }
+        return columns
 
 def sanityCheckDatabase(connection, sanity_check):
-	sanity_check(connection).check()
+    sanity_check(connection).check()
 
 class DBSanityCheck(object):
-	def __init__(self, connection):
-		self.connection = connection
+    def __init__(self, connection):
+        self.connection = connection
 
-	def check(self):
-		pass
+    def check(self):
+        pass
 
 # ===============
 # = Upgrade API =
 # ===============
 
 def upgradeDatabase(connection, schema):
-	logger.log(u"Checking database structure...", logger.MESSAGE)
-	_processUpgrade(connection, schema)
+    logger.log(u"Checking database structure...", logger.MESSAGE)
+    _processUpgrade(connection, schema)
 
 def prettyName(str):
-	return ' '.join([x.group() for x in re.finditer("([A-Z])([a-z0-9]+)", str)])
+    return ' '.join([x.group() for x in re.finditer("([A-Z])([a-z0-9]+)", str)])
 
 def _processUpgrade(connection, upgradeClass):
-	instance = upgradeClass(connection)
-	logger.log(u"Checking " + prettyName(upgradeClass.__name__) + " database upgrade", logger.DEBUG)
-	if not instance.test():
-		logger.log(u"Database upgrade required: " + prettyName(upgradeClass.__name__), logger.MESSAGE)
-		try:
-			instance.execute()
-		except sqlite3.DatabaseError, e:
-			print "Error in " + str(upgradeClass.__name__) + ": " + str(e)
-			raise
-		logger.log(upgradeClass.__name__ + " upgrade completed", logger.DEBUG)
-	else:
-		logger.log(upgradeClass.__name__ + " upgrade not required", logger.DEBUG)
-
-	for upgradeSubClass in upgradeClass.__subclasses__():
-		_processUpgrade(connection, upgradeSubClass)
+    instance = upgradeClass(connection)
+    logger.log(u"Checking " + prettyName(upgradeClass.__name__) + " database upgrade", logger.DEBUG)
+    if not instance.test():
+        logger.log(u"Database upgrade required: " + prettyName(upgradeClass.__name__), logger.MESSAGE)
+        try:
+            instance.execute()
+        except sqlite3.DatabaseError, e:
+            print "Error in " + str(upgradeClass.__name__) + ": " + ex(e)
+            raise
+        logger.log(upgradeClass.__name__ + " upgrade completed", logger.DEBUG)
+    else:
+        logger.log(upgradeClass.__name__ + " upgrade not required", logger.DEBUG)
+
+    for upgradeSubClass in upgradeClass.__subclasses__():
+        _processUpgrade(connection, upgradeSubClass)
 
 # Base migration class. All future DB changes should be subclassed from this class
 class SchemaUpgrade (object):
-	def __init__(self, connection):
-		self.connection = connection
-
-	def hasTable(self, tableName):
-		return len(self.connection.action("SELECT 1 FROM sqlite_master WHERE name = ?;", (tableName, )).fetchall()) > 0
-
-	def hasColumn(self, tableName, column):
-		return column in self.connection.tableInfo(tableName)
-
-	def addColumn(self, table, column, type="NUMERIC", default=0):
-		self.connection.action("ALTER TABLE %s ADD %s %s" % (table, column, type))
-		self.connection.action("UPDATE %s SET %s = ?" % (table, column), (default,))
-
-	def checkDBVersion(self):
-		result = self.connection.select("SELECT db_version FROM db_version")
-		if result:
-			return int(result[0]["db_version"])
-		else:
-			return 0
-
-	def incDBVersion(self):
-		curVersion = self.checkDBVersion()
-		self.connection.action("UPDATE db_version SET db_version = ?", [curVersion+1])
-		return curVersion+1
+    def __init__(self, connection):
+        self.connection = connection
+
+    def hasTable(self, tableName):
+        return len(self.connection.action("SELECT 1 FROM sqlite_master WHERE name = ?;", (tableName, )).fetchall()) > 0
+
+    def hasColumn(self, tableName, column):
+        return column in self.connection.tableInfo(tableName)
+
+    def addColumn(self, table, column, type="NUMERIC", default=0):
+        self.connection.action("ALTER TABLE %s ADD %s %s" % (table, column, type))
+        self.connection.action("UPDATE %s SET %s = ?" % (table, column), (default,))
+
+    def checkDBVersion(self):
+        result = self.connection.select("SELECT db_version FROM db_version")
+        if result:
+            return int(result[0]["db_version"])
+        else:
+            return 0
+
+    def incDBVersion(self):
+        curVersion = self.checkDBVersion()
+        self.connection.action("UPDATE db_version SET db_version = ?", [curVersion+1])
+        return curVersion+1
diff --git a/sickbeard/encodingKludge.py b/sickbeard/encodingKludge.py
index 6b505808c3d12f2b6d891f44e684c40fd71503b5..c2ea784257688c3a9dc5519247defccd70c77d2a 100644
--- a/sickbeard/encodingKludge.py
+++ b/sickbeard/encodingKludge.py
@@ -17,8 +17,6 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
-import os.path
-import locale
 
 from sickbeard import logger
 import sickbeard
diff --git a/sickbeard/exceptions.py b/sickbeard/exceptions.py
index a72d13140509ccd240750888f613e6739fbb869c..52e328c40088d32b07cc9ce0ebeb520af187f1c2 100644
--- a/sickbeard/exceptions.py
+++ b/sickbeard/exceptions.py
@@ -16,7 +16,28 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-
+from sickbeard.encodingKludge import fixStupidEncodings
+
+def ex(e):
+	"""
+	Returns a unicode string from the exception text if it exists.
+	"""
+	
+	# sanity check
+	if not e.args or not e.args[0]:
+		return ""
+
+	e_message = fixStupidEncodings(e.args[0])
+	
+	# if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway
+	if not e_message:
+		try:
+			e_message = str(e.args[0])
+		except:
+			e_message = ""
+	
+	return e_message
+	
 
 class SickBeardException(Exception):
 	"Generic SickBeard Exception - should never be thrown, only subclassed"
diff --git a/sickbeard/generic_queue.py b/sickbeard/generic_queue.py
index dc543bfcaf5e7b0dd1b48c89b91d9bb5559f0999..eb1d3801c1ec519ddececa1804ab9ee0e50ecd97 100644
--- a/sickbeard/generic_queue.py
+++ b/sickbeard/generic_queue.py
@@ -16,10 +16,9 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import operator
+import datetime
 import threading
 
-import sickbeard
 from sickbeard import logger
 
 class QueuePriorities:
@@ -51,7 +50,10 @@ class GenericQueue(object):
         self.min_priority = 0
 
     def add_item(self, item):
+        item.added = datetime.datetime.now()
         self.queue.append(item)
+        
+        return item
 
     def run(self):
 
@@ -67,7 +69,21 @@ class GenericQueue(object):
             if len(self.queue) > 0:
 
                 # sort by priority
-                self.queue.sort(key=operator.attrgetter('priority'), reverse=True)
+                def sorter(x,y):
+                    """
+                    Sorts by priority descending then time ascending
+                    """
+                    if x.priority == y.priority:
+                        if y.added == x.added:
+                            return 0
+                        elif y.added < x.added:
+                            return 1
+                        elif y.added > x.added:
+                            return -1
+                    else:
+                        return y.priority-x.priority
+
+                self.queue.sort(cmp=sorter)
                 
                 queueItem = self.queue[0]
 
@@ -96,6 +112,8 @@ class QueueItem:
         self.thread_name = None
 
         self.action_id = action_id
+        
+        self.added = None
 
     def get_thread_name(self):
         if self.thread_name:
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index 47cbc0ab5ad59f68646636bb61722168a9e7850d..959f0815181afb0d42fa505bb7e804a4cb1c014e 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -18,20 +18,21 @@
 
 
 import StringIO, zlib, gzip
-import os.path, os
+import os
 import stat
 import urllib, urllib2
-import re
+import re, socket
 import shutil
 
 import sickbeard
 
-from sickbeard.exceptions import *
+from sickbeard.exceptions import MultipleShowObjectsException
 from sickbeard import logger, classes
-from sickbeard.common import *
+from sickbeard.common import USER_AGENT, mediaExtensions, XML_NSMAP
 
 from sickbeard import db
 from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
 
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
@@ -124,17 +125,21 @@ def getURL (url, headers=[]):
 
     encoding = usock.info().get("Content-Encoding")
 
-    if encoding in ('gzip', 'x-gzip', 'deflate'):
-        content = usock.read()
-        if encoding == 'deflate':
-            data = StringIO.StringIO(zlib.decompress(content))
-        else:
-            data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(content))
-        result = data.read()
+    try:
+        if encoding in ('gzip', 'x-gzip', 'deflate'):
+            content = usock.read()
+            if encoding == 'deflate':
+                data = StringIO.StringIO(zlib.decompress(content))
+            else:
+                data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(content))
+            result = data.read()
 
-    else:
-        result = usock.read()
-        usock.close()
+        else:
+            result = usock.read()
+            usock.close()
+    except socket.timeout:
+        logger.log(u"Timed out while loading URL "+url, logger.WARNING)
+        return None
 
     return result
 
@@ -407,7 +412,7 @@ def rename_file(old_path, new_name):
     try:
         ek.ek(os.rename, old_path, new_path)
     except (OSError, IOError), e:
-        logger.log(u"Failed renaming " + old_path + " to " + new_path + ": " + str(e), logger.ERROR)
+        logger.log(u"Failed renaming " + old_path + " to " + new_path + ": " + ex(e), logger.ERROR)
         return False
 
     return new_path
@@ -417,6 +422,11 @@ def chmodAsParent(childPath):
         return
 
     parentPath = ek.ek(os.path.dirname, childPath)
+    
+    if not parentPath:
+        logger.log(u"No parent path provided in "+childPath+", unable to get permissions from it", logger.DEBUG)
+        return
+    
     parentMode = stat.S_IMODE(os.stat(parentPath)[stat.ST_MODE])
 
     if ek.ek(os.path.isfile, childPath):
@@ -453,7 +463,7 @@ def fixSetGroupID(childPath):
             return
 
         try:
-            ek.ek(os.chown, childPath, -1, parentGID)
+            ek.ek(os.chown, childPath, -1, parentGID)  #@UndefinedVariable - only available on UNIX
             logger.log(u"Respecting the set-group-ID bit on the parent directory for %s" % (childPath), logger.DEBUG)
         except OSError:
             logger.log(u"Failed to respect the set-group-ID bit on the parent directory for %s (setting group ID %i)" % (childPath, parentGID), logger.ERROR)
diff --git a/sickbeard/history.py b/sickbeard/history.py
index a2eb4ce97e9025421b4d5983d7dfe992cfd9e0f0..87cd9d427c6e394b6e86627406800263bb381d9d 100644
--- a/sickbeard/history.py
+++ b/sickbeard/history.py
@@ -17,12 +17,9 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import db
-import sqlite3
 import datetime
 
-from sickbeard import logger
-from sickbeard.common import *
-from sickbeard import providers
+from sickbeard.common import SNATCHED, Quality
 
 dateFormat = "%Y%m%d%H%M%S"
 
diff --git a/sickbeard/image_cache.py b/sickbeard/image_cache.py
index 4f9640235751b67b951edcc853dcd916361ba837..0a485a5a21a15261edaa21bdaf18da45a6521476 100644
--- a/sickbeard/image_cache.py
+++ b/sickbeard/image_cache.py
@@ -17,8 +17,6 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import os.path
-import sys
-import traceback
 
 import sickbeard
 
@@ -214,7 +212,7 @@ class ImageCache:
                         logger.log(u"Found an image in the show dir that doesn't exist in the cache, caching it: "+cur_file_name+", type "+str(cur_file_type), logger.DEBUG)
                         self._cache_image_from_file(cur_file_name, cur_file_type, show_obj.tvdbid)
                         need_images[cur_file_type] = False
-        except exceptions.ShowDirNotFoundException, e:
+        except exceptions.ShowDirNotFoundException:
             logger.log(u"Unable to search for images in show dir because it doesn't exist", logger.WARNING)
                     
         # download from TVDB for missing ones
diff --git a/sickbeard/logger.py b/sickbeard/logger.py
index 8e1ca30a19cf2c378d5250574f3c42caf0ad1217..28bdc5e8985c54987efc4c3bf8634a8b402220d5 100644
--- a/sickbeard/logger.py
+++ b/sickbeard/logger.py
@@ -18,18 +18,22 @@
 
 from __future__ import with_statement 
 
-import os.path
+import os
 import threading
 
 import logging
-import logging.handlers
-
-from exceptions import *
 
 import sickbeard
 
 from sickbeard import classes
 
+
+# number of log files to keep
+NUM_LOGS = 3
+
+# log size in bytes
+LOG_SIZE = 10000000 # 10 megs
+
 ERROR = logging.ERROR
 WARNING = logging.WARNING
 MESSAGE = logging.INFO
@@ -40,63 +44,136 @@ reverseNames = {u'ERROR': ERROR,
                 u'INFO': MESSAGE,
                 u'DEBUG': DEBUG}
 
-logFile = ''
-
-log_lock = threading.Lock()
-
-def initLogging(consoleLogging=True):
-    global logFile
-
-    logFile = os.path.join(sickbeard.LOG_DIR, 'sickbeard.log')
-
-    fileHandler = logging.handlers.RotatingFileHandler(
-                  logFile,
-                  maxBytes=25000000,
-                  backupCount=5)
-
-    fileHandler.setLevel(logging.DEBUG)
-    fileHandler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s', '%b-%d %H:%M:%S'))
-
-    logging.getLogger('sickbeard').addHandler(fileHandler)
-
-    # define a Handler which writes INFO messages or higher to the sys.stderr
-    if consoleLogging:
-        console = logging.StreamHandler()
+class SBRotatingLogHandler(object):
 
-        console.setLevel(logging.INFO)
-
-        # set a format which is simpler for console use
-        console.setFormatter(logging.Formatter('%(asctime)s %(levelname)s::%(message)s', '%H:%M:%S'))
-
-        # add the handler to the root logger
-        logging.getLogger('sickbeard').addHandler(console)
-
-    logging.getLogger('sickbeard').setLevel(logging.DEBUG)
+    def __init__(self, log_file, num_files, num_bytes):
+        self.num_files = num_files
+        self.num_bytes = num_bytes
+        
+        self.log_file = log_file
+        self.cur_handler = None
 
-def log(toLog, logLevel=MESSAGE):
+        self.writes_since_check = 0
 
-    with log_lock:
+        self.log_lock = threading.Lock()
 
-        meThread = threading.currentThread().getName()
-        message = meThread + u" :: " + toLog
+    def initLogging(self, consoleLogging=True):
+    
+        self.log_file = os.path.join(sickbeard.LOG_DIR, self.log_file)
+    
+        self.cur_handler = self._config_handler()
+    
+        logging.getLogger('sickbeard').addHandler(self.cur_handler)
+    
+        # define a Handler which writes INFO messages or higher to the sys.stderr
+        if consoleLogging:
+            console = logging.StreamHandler()
+    
+            console.setLevel(logging.INFO)
     
-        outLine = message.encode('utf-8')
+            # set a format which is simpler for console use
+            console.setFormatter(logging.Formatter('%(asctime)s %(levelname)s::%(message)s', '%H:%M:%S'))
     
-        sbLogger = logging.getLogger('sickbeard')
+            # add the handler to the root logger
+            logging.getLogger('sickbeard').addHandler(console)
+    
+        logging.getLogger('sickbeard').setLevel(logging.DEBUG)
 
-        try:
-            if logLevel == DEBUG:
-                sbLogger.debug(outLine)
-            elif logLevel == MESSAGE:
-                sbLogger.info(outLine)
-            elif logLevel == WARNING:
-                sbLogger.warning(outLine)
-            elif logLevel == ERROR:
-                sbLogger.error(outLine)
+    def _config_handler(self):
+        """
+        Configure a file handler to log at file_name and return it.
+        """
+    
+        file_handler = logging.FileHandler(self.log_file)
+        file_handler.setLevel(logging.DEBUG)
+        file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s', '%b-%d %H:%M:%S'))
+        return file_handler
+
+    def _log_file_name(self, i):
+        """
+        Returns a numbered log file name depending on i. If i==0 it just uses logName, if not it appends
+        it to the extension (blah.log.3 for i == 3)
+        
+        i: Log number to ues
+        """
+        return self.log_file + ('.' + str(i) if i else '')
+    
+    def _num_logs(self):
+        """
+        Scans the log folder and figures out how many log files there are already on disk
+        
+        Returns: The number of the last used file (eg. mylog.log.3 would return 3). If there are no logs it returns -1
+        """
+        cur_log = 0
+        while os.path.isfile(self._log_file_name(cur_log)):
+            cur_log += 1
+        return cur_log - 1
+    
+    def _rotate_logs(self):
+        
+        sb_logger = logging.getLogger('sickbeard')
+        
+        # delete the old handler
+        if self.cur_handler:
+            self.cur_handler.flush()
+            self.cur_handler.close()
+            sb_logger.removeHandler(self.cur_handler)
+    
+        # rename or delete all the old log files
+        for i in range(self._num_logs(), -1, -1):
+            cur_file_name = self._log_file_name(i)
+            try:
+                if i >= NUM_LOGS:
+                    os.remove(cur_file_name)
+                else:
+                    os.rename(cur_file_name, self._log_file_name(i+1))
+            except WindowsError:
+                pass
+        
+        # the new log handler will always be on the un-numbered .log file
+        new_file_handler = self._config_handler()
         
-                # add errors to the UI logger
-                classes.ErrorViewer.add(classes.UIError(message))
+        self.cur_handler = new_file_handler
+        
+        sb_logger.addHandler(new_file_handler)
+
+    def log(self, toLog, logLevel=MESSAGE):
+    
+        with self.log_lock:
+    
+            # check the size and see if we need to rotate
+            if self.writes_since_check >= 10:
+                if os.path.isfile(self.log_file) and os.path.getsize(self.log_file) >= LOG_SIZE:
+                    self._rotate_logs()
+                self.writes_since_check = 0
             else:
-                sbLogger.log(logLevel, outLine)
-        except ValueError, e:
-            pass
\ No newline at end of file
+                self.writes_since_check += 1
+    
+            meThread = threading.currentThread().getName()
+            message = meThread + u" :: " + toLog
+        
+            out_line = message.encode('utf-8')
+        
+            sb_logger = logging.getLogger('sickbeard')
+    
+            try:
+                if logLevel == DEBUG:
+                    sb_logger.debug(out_line)
+                elif logLevel == MESSAGE:
+                    sb_logger.info(out_line)
+                elif logLevel == WARNING:
+                    sb_logger.warning(out_line)
+                elif logLevel == ERROR:
+                    sb_logger.error(out_line)
+            
+                    # add errors to the UI logger
+                    classes.ErrorViewer.add(classes.UIError(message))
+                else:
+                    sb_logger.log(logLevel, out_line)
+            except ValueError:
+                pass
+
+sb_log_instance = SBRotatingLogHandler('sickbeard.log', NUM_LOGS, LOG_SIZE)
+
+def log(toLog, logLevel=MESSAGE):
+    sb_log_instance.log(toLog, logLevel)
\ No newline at end of file
diff --git a/sickbeard/metadata/generic.py b/sickbeard/metadata/generic.py
index c31150a343d300f6227cc611ecabeec89fb222bb..b303aca8de098ef3ba71618d5a7d7c5bbb806fb0 100644
--- a/sickbeard/metadata/generic.py
+++ b/sickbeard/metadata/generic.py
@@ -24,17 +24,15 @@ import re
 
 import sickbeard
 
-from sickbeard.common import *
-from sickbeard import logger, exceptions, helpers
-from sickbeard import encodingKludge as ek
+from sickbeard import exceptions, helpers
 from sickbeard.metadata import helpers as metadata_helpers
+from sickbeard import logger
+from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
 
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
 
-from sickbeard import logger
-from sickbeard import encodingKludge as ek
-
 class GenericMetadata():
     """
     Base class for all metadata providers. Default behavior is meant to mostly
@@ -244,9 +242,9 @@ class GenericMetadata():
             t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms)
             tvdb_show_obj = t[ep_obj.show.tvdbid]
         except tvdb_exceptions.tvdb_shownotfound, e:
-            raise exceptions.ShowNotFoundException(str(e))
+            raise exceptions.ShowNotFoundException(e.message)
         except tvdb_exceptions.tvdb_error, e:
-            logger.log(u"Unable to connect to TVDB while creating meta files - skipping - "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to connect to TVDB while creating meta files - skipping - "+ex(e), logger.ERROR)
             return None
     
         # try all included episodes in case some have thumbs and others don't
@@ -301,7 +299,7 @@ class GenericMetadata():
             nfo_file.close()
             helpers.chmodAsParent(nfo_file_path)
         except IOError, e:
-            logger.log(u"Unable to write file to "+nfo_file_path+" - are you sure the folder is writable? "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to write file to "+nfo_file_path+" - are you sure the folder is writable? "+ex(e), logger.ERROR)
             return False
         
         return True
@@ -345,7 +343,7 @@ class GenericMetadata():
             nfo_file.close()
             helpers.chmodAsParent(nfo_file_path)
         except IOError, e:
-            logger.log(u"Unable to write file to "+nfo_file_path+" - are you sure the folder is writable? "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to write file to "+nfo_file_path+" - are you sure the folder is writable? "+ex(e), logger.ERROR)
             return False
         
         return True
@@ -453,7 +451,7 @@ class GenericMetadata():
                 continue
     
             # Just grab whatever's there for now
-            art_id, season_url = cur_season_art.popitem()
+            art_id, season_url = cur_season_art.popitem() #@UnusedVariable
 
             season_thumb_file_path = self.get_season_thumb_path(show_obj, cur_season)
             
@@ -502,7 +500,7 @@ class GenericMetadata():
             outFile.close()
             helpers.chmodAsParent(image_path)
         except IOError, e:
-            logger.log(u"Unable to write image to "+image_path+" - are you sure the show folder is writable? "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to write image to "+image_path+" - are you sure the show folder is writable? "+ex(e), logger.ERROR)
             return False
     
         return True
@@ -531,7 +529,7 @@ class GenericMetadata():
             t = tvdb_api.Tvdb(banners=True, **ltvdb_api_parms)
             tvdb_show_obj = t[show_obj.tvdbid]
         except (tvdb_exceptions.tvdb_error, IOError), e:
-            logger.log(u"Unable to look up show on TVDB, not downloading images: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to look up show on TVDB, not downloading images: "+ex(e), logger.ERROR)
             return None
     
         if image_type not in ('fanart', 'poster', 'banner'):
@@ -568,7 +566,7 @@ class GenericMetadata():
             t = tvdb_api.Tvdb(banners=True, **ltvdb_api_parms)
             tvdb_show_obj = t[show_obj.tvdbid]
         except (tvdb_exceptions.tvdb_error, IOError), e:
-            logger.log(u"Unable to look up show on TVDB, not downloading images: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to look up show on TVDB, not downloading images: "+ex(e), logger.ERROR)
             return result
     
         #  How many seasons?
@@ -635,7 +633,7 @@ class GenericMetadata():
                 return empty_return
     
         except (exceptions.NoNFOException, SyntaxError, ValueError), e:
-            logger.log(u"There was an error parsing your existing metadata file: " + str(e), logger.WARNING)
+            logger.log(u"There was an error parsing your existing metadata file: " + ex(e), logger.WARNING)
             return empty_return
     
         return (tvdb_id, name)
diff --git a/sickbeard/metadata/helpers.py b/sickbeard/metadata/helpers.py
index f773016aab46f2f92e17c21813dff88d1114e568..5acd822de7c4f4be7e78a0ff4d92c7b04f954a76 100644
--- a/sickbeard/metadata/helpers.py
+++ b/sickbeard/metadata/helpers.py
@@ -17,14 +17,13 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
-import os.path
 import urllib2
 
 import sickbeard
 
-from sickbeard.common import *
 from sickbeard import logger, exceptions, helpers
 from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
 
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
@@ -32,8 +31,6 @@ import xml.etree.cElementTree as etree
 
 def getTVDBIDFromNFO(dir):
 
-    show_lang = None
-
     if not ek.ek(os.path.isdir, dir):
         logger.log(u"Show dir doesn't exist, can't load NFO")
         raise exceptions.NoNFOException("The show dir doesn't exist, no NFO could be loaded")
@@ -52,7 +49,6 @@ def getTVDBIDFromNFO(dir):
                 + str(showXML.findtext('tvdbid')) + " " \
                 + str(showXML.findtext('id')))
 
-        name = showXML.findtext('title')
         if showXML.findtext('tvdbid') != None:
             tvdb_id = int(showXML.findtext('tvdbid'))
         elif showXML.findtext('id'):
@@ -69,14 +65,14 @@ def getTVDBIDFromNFO(dir):
             raise exceptions.NoNFOException("Unable to look up the show on TVDB, not using the NFO")
 
     except (exceptions.NoNFOException, SyntaxError, ValueError), e:
-        logger.log(u"There was an error parsing your existing tvshow.nfo file: " + str(e), logger.ERROR)
+        logger.log(u"There was an error parsing your existing tvshow.nfo file: " + ex(e), logger.ERROR)
         logger.log(u"Attempting to rename it to tvshow.nfo.old", logger.DEBUG)
 
         try:
             xmlFileObj.close()
             ek.ek(os.rename, xmlFile, xmlFile + ".old")
         except Exception, e:
-            logger.log(u"Failed to rename your tvshow.nfo file - you need to delete it or fix it: " + str(e), logger.ERROR)
+            logger.log(u"Failed to rename your tvshow.nfo file - you need to delete it or fix it: " + ex(e), logger.ERROR)
         raise exceptions.NoNFOException("Invalid info in tvshow.nfo")
 
     return tvdb_id
@@ -101,7 +97,7 @@ def getShowImage(url, imgNum=None):
         logger.log(u"There was an error trying to retrieve the image, aborting", logger.ERROR)
         return None
     except urllib2.HTTPError, e:
-        logger.log(u"Unable to access image at "+tempURL+", assuming it doesn't exist: "+str(e).decode('utf-8'), logger.ERROR)
+        logger.log(u"Unable to access image at "+tempURL+", assuming it doesn't exist: "+ex(e), logger.ERROR)
         return None
 
     return image_data
diff --git a/sickbeard/metadata/mediabrowser.py b/sickbeard/metadata/mediabrowser.py
index e5f2629ca64c30339a99ba13207c041b6767ef9b..cf9536ee5d64dd6b79a7fecb0f04475b19e6fc9d 100644
--- a/sickbeard/metadata/mediabrowser.py
+++ b/sickbeard/metadata/mediabrowser.py
@@ -16,16 +16,19 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import datetime, re
+import datetime
+import os
+import re
 
 import sickbeard
 
 import generic
 
-from sickbeard.common import *
+from sickbeard.common import XML_NSMAP
 from sickbeard import logger, exceptions, helpers
 from sickbeard import encodingKludge as ek
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
+from sickbeard.exceptions import ex
 
 import xml.etree.cElementTree as etree
 
@@ -264,8 +267,6 @@ class MediaBrowserMetadata(generic.GenericMetadata):
         
         eps_to_write = [ep_obj] + ep_obj.relatedEps
         
-        shouldSave = False
-
         tvdb_lang = ep_obj.show.lang
     
         try:
@@ -279,9 +280,9 @@ class MediaBrowserMetadata(generic.GenericMetadata):
             t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms)
             myShow = t[ep_obj.show.tvdbid]
         except tvdb_exceptions.tvdb_shownotfound, e:
-            raise exceptions.ShowNotFoundException(str(e))
+            raise exceptions.ShowNotFoundException(e.message)
         except tvdb_exceptions.tvdb_error, e:
-            logger.log("Unable to connect to TVDB while creating meta files - skipping - "+str(e), logger.ERROR)
+            logger.log("Unable to connect to TVDB while creating meta files - skipping - "+ex(e), logger.ERROR)
             return False
 
         rootNode = etree.Element("Item")
diff --git a/sickbeard/metadata/ps3.py b/sickbeard/metadata/ps3.py
index 6fa2d34cb3353f6cd7579ac53412b6c93f25af60..39c1f3d0d553f516a9be897af7c11c800781c672 100644
--- a/sickbeard/metadata/ps3.py
+++ b/sickbeard/metadata/ps3.py
@@ -16,16 +16,10 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import datetime
-
-import sickbeard
+import os
 
 import generic
 
-from sickbeard.common import *
-from sickbeard import logger, exceptions, helpers
-from lib.tvdb_api import tvdb_api, tvdb_exceptions
-
 from sickbeard import encodingKludge as ek
 
 class PS3Metadata(generic.GenericMetadata):
diff --git a/sickbeard/metadata/wdtv.py b/sickbeard/metadata/wdtv.py
index b06a39801574968700bceecc0002c51af9f0e7a7..94c5dced06350fecfb1a73c255fec3f08510fdf7 100644
--- a/sickbeard/metadata/wdtv.py
+++ b/sickbeard/metadata/wdtv.py
@@ -16,15 +16,12 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import datetime
-
-import sickbeard
+import os
+import re
 
 import generic
 
-from sickbeard.common import *
-from sickbeard import logger, exceptions, helpers
-from lib.tvdb_api import tvdb_api, tvdb_exceptions
+from sickbeard import logger, helpers
 
 from sickbeard import encodingKludge as ek
 
diff --git a/sickbeard/metadata/xbmc.py b/sickbeard/metadata/xbmc.py
index 8dd648fcd05906472d81184e8728e01fb17cd15d..f1d760a35f2b620b5179319884e023aa06c72be4 100644
--- a/sickbeard/metadata/xbmc.py
+++ b/sickbeard/metadata/xbmc.py
@@ -22,9 +22,10 @@ import sickbeard
 
 import generic
 
-from sickbeard.common import *
+from sickbeard.common import XML_NSMAP
 from sickbeard import logger, exceptions, helpers
-from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
+
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
 import xml.etree.cElementTree as etree
@@ -188,9 +189,9 @@ class XBMCMetadata(generic.GenericMetadata):
             t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms)
             myShow = t[ep_obj.show.tvdbid]
         except tvdb_exceptions.tvdb_shownotfound, e:
-            raise exceptions.ShowNotFoundException(str(e))
+            raise exceptions.ShowNotFoundException(e.message)
         except tvdb_exceptions.tvdb_error, e:
-            logger.log(u"Unable to connect to TVDB while creating meta files - skipping - "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to connect to TVDB while creating meta files - skipping - "+ex(e), logger.ERROR)
             return
 
         if len(eps_to_write) > 1:
diff --git a/sickbeard/name_cache.py b/sickbeard/name_cache.py
index a0629f4ce226eeaa28ad992a94b790aa0cc913b0..d29e1bee19db66ce4f40ab0c798016ff402927bc 100644
--- a/sickbeard/name_cache.py
+++ b/sickbeard/name_cache.py
@@ -19,8 +19,6 @@
 from sickbeard import db
 from sickbeard.helpers import sanitizeSceneName
 
-from sickbeard import logger
-
 def addNameToCache(name, tvdb_id):
     """
     Adds the show & tvdb id to the scene_names table in cache.db.
diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py
index dd8c31f5f79d73477702fb221adf7e5e4c8d025e..ed844bcdea1b501b37a8d586d4cfc5d11997ff3a 100644
--- a/sickbeard/name_parser/parser.py
+++ b/sickbeard/name_parser/parser.py
@@ -22,6 +22,8 @@ import re
 
 import regexes
 
+import sickbeard
+
 from sickbeard import logger
 
 class NameParser(object):
@@ -111,7 +113,7 @@ class NameParser(object):
                 try:
                     result.air_date = datetime.date(year, month, day)
                 except ValueError, e:
-                    raise InvalidNameException(str(e))
+                    raise InvalidNameException(e.message)
 
             if 'extra_info' in named_groups:
                 tmp_extra_info = match.group('extra_info')
@@ -228,7 +230,7 @@ class NameParser(object):
 
         # if there's no useful info in it then raise an exception
         if final_result.season_number == None and not final_result.episode_numbers and final_result.air_date == None and not final_result.series_name:
-            raise InvalidNameException("Unable to parse "+name)
+            raise InvalidNameException("Unable to parse "+name.encode(sickbeard.SYS_ENCODING))
 
         # return it
         return final_result
diff --git a/sickbeard/notifiers/growl.py b/sickbeard/notifiers/growl.py
index 68f94edde83d7df98b2442eb235fe6a9d16c3823..d4bcc1a143e3e828bdaa7aab038b93543f76daad 100644
--- a/sickbeard/notifiers/growl.py
+++ b/sickbeard/notifiers/growl.py
@@ -17,17 +17,18 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import socket
-import sys
 
 import sickbeard
 
 from sickbeard import logger, common
+from sickbeard.exceptions import ex
 
 from lib.growl import gntp
 
 class GrowlNotifier:
 
     def test_notify(self, host, password):
+        self._sendRegistration(host, password, 'Test')
         return self._sendGrowl("Test Growl", "Testing Growl settings from Sick Beard", "Test", host, password, force=True)
 
     def notify_snatch(self, ep_name):
@@ -39,17 +40,7 @@ class GrowlNotifier:
             self._sendGrowl(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_name)
 
     def _send_growl(self, options,message=None):
-    
-        #Send Registration
-        register = gntp.GNTPRegister()
-        register.add_header('Application-Name',options['app'])
-        register.add_notification(options['name'],True)
-    
-        if options['password']:
-            register.set_password(options['password'])
-    
-        self._send(options['host'],options['port'],register.encode(),options['debug'])
-    
+                
         #Send Notification
         notice = gntp.GNTPNotice()
     
@@ -67,7 +58,7 @@ class GrowlNotifier:
         if options['priority']:
             notice.add_header('Notification-Priority',options['priority'])
         if options['icon']:
-            notice.add_header('Notification-Icon',options['icon'])
+            notice.add_header('Notification-Icon', 'https://github.com/midgetspy/Sick-Beard/raw/master/data/images/sickbeard_touch_icon.png')
     
         if message:
             notice.add_header('Notification-Text',message)
@@ -79,7 +70,6 @@ class GrowlNotifier:
     def _send(self, host,port,data,debug=False):
         if debug: print '<Sending>\n',data,'\n</Sending>'
         
-        response = ''
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         s.connect((host,port))
         s.send(data)
@@ -91,7 +81,6 @@ class GrowlNotifier:
         return response
 
     def _sendGrowl(self, title="Sick Beard Notification", message=None, name=None, host=None, password=None, force=False):
-    
         if not sickbeard.USE_GROWL and not force:
             return False
     
@@ -126,7 +115,7 @@ class GrowlNotifier:
         else:
             opts['password'] = password
     
-        opts['icon'] = False
+        opts['icon'] = True
     
     
         for pc in growlHosts:
@@ -136,7 +125,53 @@ class GrowlNotifier:
             try:
                 return self._send_growl(opts, message)
             except socket.error, e:
-                logger.log(u"Unable to send growl to "+opts['host']+":"+str(opts['port'])+": "+str(e).decode('utf-8'))
+                logger.log(u"Unable to send growl to "+opts['host']+":"+str(opts['port'])+": "+ex(e))
                 return False
 
+    def _sendRegistration(self, host=None, password=None, name='Sick Beard Notification'):
+        opts = {}
+    
+        if host == None:
+            hostParts = sickbeard.GROWL_HOST.split(':')
+        else:
+            hostParts = host.split(':')
+    
+        if len(hostParts) != 2 or hostParts[1] == '':
+            port = 23053
+        else:
+            port = int(hostParts[1])
+            
+        opts['host'] = hostParts[0]
+        opts['port'] = port
+        
+            
+        if password == None:
+            opts['password'] = sickbeard.GROWL_PASSWORD
+        else:
+            opts['password'] = password
+    
+        
+        opts['app'] = 'SickBeard'
+        opts['debug'] = False
+        
+        #Send Registration
+        register = gntp.GNTPRegister()
+        register.add_header('Application-Name', opts['app'])
+        register.add_header('Application-Icon', 'https://github.com/midgetspy/Sick-Beard/raw/master/data/images/sickbeard_touch_icon.png')
+        
+        register.add_notification('Test', True)
+        register.add_notification(common.notifyStrings[common.NOTIFY_SNATCH], True)
+        register.add_notification(common.notifyStrings[common.NOTIFY_DOWNLOAD], True)
+
+        if opts['password']:
+            register.set_password(opts['password'])
+        
+        try:
+            return self._send(opts['host'],opts['port'],register.encode(),opts['debug'])
+        except socket.error, e:
+            logger.log(u"Unable to send growl to "+opts['host']+":"+str(opts['port'])+": "+str(e).decode('utf-8'))
+            return False
+        
+    
+    
 notifier = GrowlNotifier
\ No newline at end of file
diff --git a/sickbeard/notifiers/libnotify.py b/sickbeard/notifiers/libnotify.py
index 13cda96abfb0cbd8dce730cac574045a7118a378..a95f7d9deef758e00f58849cbd695039359e4e85 100644
--- a/sickbeard/notifiers/libnotify.py
+++ b/sickbeard/notifiers/libnotify.py
@@ -28,7 +28,7 @@ def diagnose():
     user-readable message indicating possible issues.
     '''
     try:
-        import pynotify
+        import pynotify  #@UnusedImport
     except ImportError:
         return (u"<p>Error: pynotify isn't installed.  On Ubuntu/Debian, install the "
                 u"<a href=\"apt:python-notify\">python-notify</a> package.")
@@ -58,6 +58,7 @@ def diagnose():
 class LibnotifyNotifier:
     def __init__(self):
         self.pynotify = None
+        self.gobject = None
 
     def init_pynotify(self):
         if self.pynotify is not None:
@@ -67,10 +68,16 @@ class LibnotifyNotifier:
         except ImportError:
             logger.log(u"Unable to import pynotify. libnotify notifications won't work.")
             return False
+        try:
+            import gobject
+        except ImportError:
+            logger.log(u"Unable to import gobject. We can't catch a GError in display.")
+            return False
         if not pynotify.init('Sick Beard'):
             logger.log(u"Initialization of pynotify failed. libnotify notifications won't work.")
             return False
         self.pynotify = pynotify
+        self.gobject = gobject
         return True
 
     def notify_snatch(self, ep_name):
@@ -99,7 +106,9 @@ class LibnotifyNotifier:
         # will be printed but the call to show() will still return True.
         # pynotify doesn't seem too keen on error handling.
         n = self.pynotify.Notification(title, message, icon_uri)
-        return n.show()
-
+        try:
+            return n.show()
+        except self.gobject.GError:
+            return False
 
 notifier = LibnotifyNotifier
diff --git a/sickbeard/notifiers/notifo.py b/sickbeard/notifiers/notifo.py
index 24b904b639355906eae921f9e3f69f7fc181adb1..f525c4f777a27f0985176a7dcdda9727fbaf2c7a 100644
--- a/sickbeard/notifiers/notifo.py
+++ b/sickbeard/notifiers/notifo.py
@@ -1,4 +1,5 @@
 # Author: Nic Wolfe <nic@wolfeden.ca>
+# Revised by: Shawn Conroyd - 4/12/2011
 # URL: http://code.google.com/p/sickbeard/
 #
 # This file is part of Sick Beard.
@@ -17,32 +18,34 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import urllib
+
 import sickbeard
 
-from sickbeard import logger, common
+from sickbeard import logger
 
 try:
-    import lib.simplejson as json
+    import lib.simplejson as json #@UnusedImport
 except:
-    import json
-
+    import json #@Reimport
 
 API_URL = "https://%(username)s:%(secret)s@api.notifo.com/v1/send_notification"
 
 class NotifoNotifier:
 
-    def test_notify(self, username, apisecret):
-        return self._sendNotifo("This is a test notification from Sick Beard", username, apisecret)
+    def test_notify(self, username, apisecret, title="Test:"):
+        return self._sendNotifo("This is a test notification from SickBeard", title, username, apisecret)
 
-    def _sendNotifo(self, msg, username, apisecret):
+    def _sendNotifo(self, msg, title, username, apisecret, label="SickBeard"):
         msg = msg.strip()
         apiurl = API_URL % {"username": username, "secret": apisecret}
         data = urllib.urlencode({
-            "msg": msg,
+            "title": title,
+            "label": label,
+            "msg": msg
         })
 
         try:
-	    data = urllib.urlopen(apiurl, data)	
+            data = urllib.urlopen(apiurl, data)    
             result = json.load(data)
         except IOError:
             return False
@@ -55,15 +58,15 @@ class NotifoNotifier:
             return True
 
 
-    def notify_snatch(self, ep_name):
+    def notify_snatch(self, ep_name, title="Snatched:"):
         if sickbeard.NOTIFO_NOTIFY_ONSNATCH:
-            self._notifyNotifo(common.notifyStrings[common.NOTIFY_SNATCH]+': '+ep_name)
+            self._notifyNotifo(title, ep_name)
 
-    def notify_download(self, ep_name):
+    def notify_download(self, ep_name, title="Completed:"):
         if sickbeard.NOTIFO_NOTIFY_ONDOWNLOAD:
-            self._notifyNotifo(common.notifyStrings[common.NOTIFY_DOWNLOAD]+': '+ep_name)       
+            self._notifyNotifo(title, ep_name)       
 
-    def _notifyNotifo(self, message=None, username=None, apisecret=None, force=False):
+    def _notifyNotifo(self, title, message=None, username=None, apisecret=None, force=False):
         if not sickbeard.USE_NOTIFO and not force:
             logger.log("Notification for Notifo not enabled, skipping this notification", logger.DEBUG)
             return False
@@ -75,7 +78,7 @@ class NotifoNotifier:
 
         logger.log(u"Sending notification for " + message, logger.DEBUG)
 
-        self._sendNotifo(message, username, apisecret)
+        self._sendNotifo(message, title, username, apisecret)
         return True
 
-notifier = NotifoNotifier
+notifier = NotifoNotifier
\ No newline at end of file
diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py
index 1401d45460a464f01a851c2b69d2a57000b552ad..2ca071e6546ea989fa24fb0bcf71e67f82bac739 100644
--- a/sickbeard/notifiers/plex.py
+++ b/sickbeard/notifiers/plex.py
@@ -19,9 +19,11 @@
 import urllib
 import sickbeard
 
-from xml.dom import minidom
 from sickbeard import logger, common
 from sickbeard.notifiers.xbmc import XBMCNotifier 
+from sickbeard.exceptions import ex
+
+from xml.dom import minidom
 
 class PLEXNotifier(XBMCNotifier):
 
@@ -67,7 +69,12 @@ class PLEXNotifier(XBMCNotifier):
         logger.log(u"Plex Media Server updating " + sickbeard.PLEX_SERVER_HOST, logger.DEBUG)
 
         url = "http://%s/library/sections" % sickbeard.PLEX_SERVER_HOST
-        xml_sections = minidom.parse(urllib.urlopen(url))
+        try:
+            xml_sections = minidom.parse(urllib.urlopen(url))
+        except IOError, e:
+            logger.log(u"Error while trying to contact your plex server: "+ex(e), logger.ERROR)
+            return False
+
         sections = xml_sections.getElementsByTagName('Directory')
 
         for s in sections:
@@ -75,9 +82,9 @@ class PLEXNotifier(XBMCNotifier):
                 url = "http://%s/library/sections/%s/refresh" % (sickbeard.PLEX_SERVER_HOST, s.getAttribute('key'))
 
                 try:
-                    x = urllib.urlopen(url)
+                    urllib.urlopen(url)
                 except Exception, e:
-                    logger.log(u"Error updating library section: "+str(e).decode('utf-8'), logger.ERROR)
+                    logger.log(u"Error updating library section: "+ex(e), logger.ERROR)
                     return False
 
         return True
diff --git a/sickbeard/notifiers/prowl.py b/sickbeard/notifiers/prowl.py
index 6491db817872cf8e9dd30791f9c446a33027ead4..01e5447de4d87f74703653679582e02c916046e8 100644
--- a/sickbeard/notifiers/prowl.py
+++ b/sickbeard/notifiers/prowl.py
@@ -16,12 +16,9 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import socket
-import sys
 from httplib import HTTPSConnection
 from urllib import urlencode
 
-
 import sickbeard
 
 from sickbeard import logger, common
diff --git a/sickbeard/notifiers/tweet.py b/sickbeard/notifiers/tweet.py
index 35a29027858bf4cd9cd9f0f7bf35fb4008f4f6ae..c56288038b60d84f66a2f6bae040534662567c0e 100644
--- a/sickbeard/notifiers/tweet.py
+++ b/sickbeard/notifiers/tweet.py
@@ -16,18 +16,16 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import socket
-import os
-import sys
 import sickbeard
 
 from sickbeard import logger, common
+from sickbeard.exceptions import ex
 
 # parse_qsl moved to urlparse module in v2.6
 try:
-    from urlparse import parse_qsl
+    from urlparse import parse_qsl #@UnusedImport
 except:
-    from cgi import parse_qsl
+    from cgi import parse_qsl #@Reimport
 
 import lib.oauth2 as oauth
 import lib.pythontwitter as twitter
@@ -55,7 +53,7 @@ class TwitterNotifier:
 
     def _get_authorization(self):
     
-        signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1()
+        signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
         oauth_consumer             = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
         oauth_client               = oauth.Client(oauth_consumer)
     
@@ -85,7 +83,7 @@ class TwitterNotifier:
     
         logger.log('Generating and signing request for an access token using key '+key)
     
-        signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1()
+        signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
         oauth_consumer             = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
         logger.log('oauth_consumer: '+str(oauth_consumer))
         oauth_client  = oauth.Client(oauth_consumer, token)
@@ -120,9 +118,9 @@ class TwitterNotifier:
         api = twitter.Api(username, password, access_token_key, access_token_secret)
     
         try:
-            status = api.PostUpdate(message)
+            api.PostUpdate(message)
         except Exception, e:
-            logger.log(u"Error Sending Tweet: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error Sending Tweet: "+ex(e), logger.ERROR)
             return False
     
         return True
diff --git a/sickbeard/notifiers/xbmc.py b/sickbeard/notifiers/xbmc.py
index 8144b0fa4ccae32de6f67bfdd1e7dca6155808c0..f967e4b88c6a9d0301a1c6c49afbb3b768286883 100644
--- a/sickbeard/notifiers/xbmc.py
+++ b/sickbeard/notifiers/xbmc.py
@@ -19,16 +19,14 @@
 
 import urllib, urllib2
 import socket
-import sys
 import base64
 import time, struct
 
-#import config
-
 import sickbeard
 
 from sickbeard import logger
 from sickbeard import common
+from sickbeard.exceptions import ex
 
 try:
     import xml.etree.cElementTree as etree
@@ -107,8 +105,7 @@ class XBMCNotifier:
             response = handle.read()
             logger.log(u"response: " + response, logger.DEBUG)
         except IOError, e:
-            # print "Warning: Couldn't contact XBMC HTTP server at " + host + ": " + str(e)
-            logger.log(u"Warning: Couldn't contact XBMC HTTP server at " + host + ": " + str(e))
+            logger.log(u"Warning: Couldn't contact XBMC HTTP server at " + host + ": " + ex(e))
             response = ''
     
         return response
@@ -182,7 +179,7 @@ class XBMCNotifier:
             try:
                 et = etree.fromstring(encSqlXML)
             except SyntaxError, e:
-                logger.log("Unable to parse XML returned from XBMC: "+str(e), logger.ERROR)
+                logger.log("Unable to parse XML returned from XBMC: "+ex(e), logger.ERROR)
                 return False
     
             paths = et.findall('.//field')
@@ -235,7 +232,7 @@ def wakeOnLan(ethernet_address):
 # Test Connection function
 def isHostUp(host,port):
 
-    (family, socktype, proto, garbage, address) = socket.getaddrinfo(host, port)[0]
+    (family, socktype, proto, garbage, address) = socket.getaddrinfo(host, port)[0] #@UnusedVariable
     s = socket.socket(family, socktype, proto)
 
     try:
diff --git a/sickbeard/nzbSplitter.py b/sickbeard/nzbSplitter.py
index 9de912fc049d304a238474e1972bb3d8e7cfeeae..4664e047370eba479ebe493f45b53c02b56052ec 100644
--- a/sickbeard/nzbSplitter.py
+++ b/sickbeard/nzbSplitter.py
@@ -25,7 +25,7 @@ import re
 from name_parser.parser import NameParser, InvalidNameException
 
 from sickbeard import logger, classes, helpers
-from sickbeard.common import *
+from sickbeard.common import Quality
 
 def getSeasonNZBs(name, urlData, season):
 
@@ -43,7 +43,7 @@ def getSeasonNZBs(name, urlData, season):
 
     sceneNameMatch = re.search(regex, filename, re.I)
     if sceneNameMatch:
-        showName, qualitySection, groupName = sceneNameMatch.groups()
+        showName, qualitySection, groupName = sceneNameMatch.groups() #@UnusedVariable
     else:
         logger.log(u"Unable to parse "+name+" into a scene name. If it's a valid one log a bug.", logger.ERROR)
         return ({},'')
@@ -102,7 +102,7 @@ def splitResult(result):
 
     try:
         urlData = helpers.getURL(result.url)
-    except urllib2.URLError, e:
+    except urllib2.URLError:
         logger.log(u"Unable to load url "+result.url+", can't download season NZB", logger.ERROR)
         return False
 
diff --git a/sickbeard/nzbget.py b/sickbeard/nzbget.py
index 3d73d1b4cda2e97beba68010b8b81b991c20785b..950b4938c477fcb481824a09090bd88817bf807d 100644
--- a/sickbeard/nzbget.py
+++ b/sickbeard/nzbget.py
@@ -28,8 +28,7 @@ import xmlrpclib
 
 from sickbeard.providers.generic import GenericProvider
 
-from sickbeard.common import *
-from sickbeard import logger, classes
+from sickbeard import logger
 
 def sendNZB(nzb):
 
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index 3dd8f229e6b443d8d7ff3a54102c5b7b9cd6e303..b0526b9c282a9ab8aca752ff5b9300f48a25ea5e 100755
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -20,7 +20,6 @@ from __future__ import with_statement
 
 import glob
 import os
-import os.path
 import re
 import shlex
 import subprocess
@@ -39,6 +38,7 @@ from sickbeard import show_name_helpers
 from sickbeard import scene_exceptions
 
 from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
 
 from sickbeard.name_parser.parser import NameParser, InvalidNameException
 
@@ -113,7 +113,7 @@ class PostProcessor(object):
         base_name = file_path.rpartition('.')[0]+'.'
         
         # don't confuse glob with chars we didn't mean to use
-        base_name = re.sub(r'[\[\]\*\?]', r'\\\g<0>', base_name)
+        base_name = re.sub(r'[\[\]\*\?]', r'[\g<0>]', base_name)
     
         for associated_file_path in ek.ek(glob.glob, base_name+'*'):
             # only list it if the only non-shared part is the extension
@@ -169,18 +169,19 @@ class PostProcessor(object):
 
             cur_file_name = ek.ek(os.path.basename, cur_file_path)
             
+            # get the extension
+            cur_extension = cur_file_path.rpartition('.')[-1]
+        
+            # replace .nfo with .nfo-orig to avoid conflicts
+            if cur_extension == 'nfo':
+                cur_extension = 'nfo-orig'
+
             # If new base name then convert name
             if new_base_name:
-                # get the extension
-                cur_extension = cur_file_path.rpartition('.')[-1]
-            
-                # replace .nfo with .nfo-orig to avoid conflicts
-                if cur_extension == 'nfo':
-                    cur_extension = 'nfo-orig'
-                    
                 new_file_name = new_base_name +'.' + cur_extension
+            # if we're not renaming we still want to change extensions sometimes
             else:
-                new_file_name = cur_file_name
+                new_file_name = helpers.replaceExtension(cur_file_name, cur_extension)
             
             new_file_path = ek.ek(os.path.join, new_path, new_file_name)
 
@@ -201,7 +202,7 @@ class PostProcessor(object):
                 helpers.moveFile(cur_file_path, new_file_path)
                 helpers.chmodAsParent(new_file_path)
             except (IOError, OSError), e:
-                self._log("Unable to move file "+cur_file_path+" to "+new_file_path+": "+str(e).decode('utf-8'), logger.ERROR)
+                self._log("Unable to move file "+cur_file_path+" to "+new_file_path+": "+ex(e), logger.ERROR)
                 raise e
                 
         self._combined_file_operation(file_path, new_path, new_base_name, associated_files, action=_int_move)
@@ -221,7 +222,7 @@ class PostProcessor(object):
                 helpers.copyFile(cur_file_path, new_file_path)
                 helpers.chmodAsParent(new_file_path)
             except (IOError, OSError), e:
-                logger.log("Unable to copy file "+cur_file_path+" to "+new_file_path+": "+str(e).decode('utf-8'), logger.ERROR)
+                logger.log("Unable to copy file "+cur_file_path+" to "+new_file_path+": "+ex(e), logger.ERROR)
                 raise e
 
         self._combined_file_operation(file_path, new_path, new_base_name, associated_files, action=_int_copy)
@@ -252,7 +253,10 @@ class PostProcessor(object):
                 if ep_obj.show.air_by_date:
                     season_folder = str(ep_obj.airdate.year)
                 else:
-                    season_folder = sickbeard.SEASON_FOLDERS_FORMAT % (ep_obj.season)
+                    try:
+                        season_folder = sickbeard.SEASON_FOLDERS_FORMAT % (ep_obj.season)
+                    except TypeError:
+                        logger.log(u"Error: Your season folder format is incorrect, try setting it back to the default")
         
         dest_folder = ek.ek(os.path.join, ep_obj.show.location, season_folder)
         
@@ -363,7 +367,7 @@ class PostProcessor(object):
     
                 self._log(u"Looking up name "+cur_name+u" on TVDB", logger.DEBUG)
                 showObj = t[cur_name]
-            except (tvdb_exceptions.tvdb_exception), e:
+            except (tvdb_exceptions.tvdb_exception):
                 # if none found, search on all languages
                 try:
                     # There's gotta be a better way of doing this but we don't wanna
@@ -375,11 +379,11 @@ class PostProcessor(object):
 
                     self._log(u"Looking up name "+cur_name+u" in all languages on TVDB", logger.DEBUG)
                     showObj = t[cur_name]
-                except (tvdb_exceptions.tvdb_exception, IOError), e:
+                except (tvdb_exceptions.tvdb_exception, IOError):
                     pass
 
                 continue
-            except (IOError), e:
+            except (IOError):
                 continue
             
             self._log(u"Lookup successful, using tvdb id "+str(showObj["id"]), logger.DEBUG)
@@ -423,10 +427,11 @@ class PostProcessor(object):
             try:
                 (cur_tvdb_id, cur_season, cur_episodes) = cur_attempt()
             except InvalidNameException, e:
-                logger.log(u"Unable to parse, skipping: "+str(e), logger.DEBUG)
+                logger.log(u"Unable to parse, skipping: "+ex(e), logger.DEBUG)
                 continue
             
-            if cur_tvdb_id:
+            # if we already did a successful history lookup then keep that tvdb_id value
+            if cur_tvdb_id and not (self.in_history and tvdb_id):
                 tvdb_id = cur_tvdb_id
             if cur_season != None:
                 season = cur_season
@@ -434,7 +439,7 @@ class PostProcessor(object):
                 episodes = cur_episodes
             
             # for air-by-date shows we need to look up the season/episode from tvdb
-            if season == -1 and tvdb_id:
+            if season == -1 and tvdb_id and episodes:
                 self._log(u"Looks like this is an air-by-date show, attempting to convert the date to season/episode", logger.DEBUG)
                 
                 # try to get language set for this show
@@ -505,7 +510,7 @@ class PostProcessor(object):
             try:
                 curEp = show_obj.getEpisode(season, episode)
             except exceptions.EpisodeNotFoundException, e:
-                self._log(u"Unable to create episode: "+str(e).decode('utf-8'), logger.DEBUG)
+                self._log(u"Unable to create episode: "+ex(e), logger.DEBUG)
                 raise exceptions.PostProcessingFailed()
     
             if root_ep == None:
@@ -519,10 +524,10 @@ class PostProcessor(object):
     def _get_quality(self, ep_obj):
         
         ep_quality = common.Quality.UNKNOWN
-        oldStatus = None
+
         # make sure the quality is set right before we continue
         if ep_obj.status in common.Quality.SNATCHED + common.Quality.SNATCHED_PROPER:
-            oldStatus, ep_quality = common.Quality.splitCompositeStatus(ep_obj.status)
+            oldStatus, ep_quality = common.Quality.splitCompositeStatus(ep_obj.status) #@UnusedVariable
             if ep_quality != common.Quality.UNKNOWN:
                 self._log(u"The old status had a quality in it, using that: "+common.Quality.qualityStrings[ep_quality], logger.DEBUG)
                 return ep_quality
@@ -557,10 +562,10 @@ class PostProcessor(object):
             self._log(u"Absolute path to script: "+ek.ek(os.path.abspath, script_cmd[0]), logger.DEBUG)
             try:
                 p = subprocess.Popen(script_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR)
-                out, err = p.communicate()
+                out, err = p.communicate() #@UnusedVariable
                 self._log(u"Script result: "+str(out), logger.DEBUG)
             except OSError, e:
-                self._log(u"Unable to run extra_script: "+str(e).decode('utf-8'))
+                self._log(u"Unable to run extra_script: "+ex(e))
     
     def _is_priority(self, ep_obj, new_ep_quality):
         
@@ -575,7 +580,7 @@ class PostProcessor(object):
             return True
         
         # if the user downloaded it manually and it appears to be a PROPER/REPACK then it's priority
-        old_ep_status, old_ep_quality = common.Quality.splitCompositeStatus(ep_obj.status)
+        old_ep_status, old_ep_quality = common.Quality.splitCompositeStatus(ep_obj.status) #@UnusedVariable
         if self.is_proper and new_ep_quality >= old_ep_quality:
             self._log(u"This was manually downloaded but it appears to be a proper so I'm marking it as priority", logger.DEBUG)
             return True 
diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py
index 4a654461451955ebc3b1e809631ee1e02f982e04..95425f2c6a6d2607974fdfcf7f7380ac8d01e29c 100644
--- a/sickbeard/processTV.py
+++ b/sickbeard/processTV.py
@@ -18,16 +18,17 @@
 
 from __future__ import with_statement
 
-import os, os.path
+import os
 import shutil
 
+import sickbeard 
 from sickbeard import postProcessor
 from sickbeard import db, helpers, exceptions
 
 from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
 
 from sickbeard import logger
-from sickbeard.common import *
 
 def logHelper (logMessage, logLevel=logger.MESSAGE):
     logger.log(logMessage, logLevel)
@@ -94,8 +95,10 @@ def processDir (dirName, nzbName=None, recurse=False):
         try:
             processor = postProcessor.PostProcessor(cur_video_file_path, nzbName)
             process_result = processor.process()
-        except exceptions.PostProcessingFailed:
+            process_fail_message = ""
+        except exceptions.PostProcessingFailed, e:
             process_result = False
+            process_fail_message = ex(e)
 
         returnStr += processor.log 
 
@@ -111,11 +114,11 @@ def processDir (dirName, nzbName=None, recurse=False):
                 try:
                     shutil.rmtree(dirName)
                 except (OSError, IOError), e:
-                    returnStr += logHelper(u"Warning: unable to remove the folder " + dirName + ": " + str(e).decode('utf-8'), logger.ERROR)
+                    returnStr += logHelper(u"Warning: unable to remove the folder " + dirName + ": " + ex(e), logger.WARNING)
 
             returnStr += logHelper(u"Processing succeeded for "+cur_video_file_path)
             
         else:
-            returnStr += logHelper(u"Processing failed for "+cur_video_file_path)
+            returnStr += logHelper(u"Processing failed for "+cur_video_file_path+": "+process_fail_message, logger.WARNING)
 
     return returnStr
diff --git a/sickbeard/properFinder.py b/sickbeard/properFinder.py
index e21c70354c0289419ce7f3c8605debe923065a18..3cbba8e7282819123c238d0e86cad55868dafa87 100644
--- a/sickbeard/properFinder.py
+++ b/sickbeard/properFinder.py
@@ -22,12 +22,12 @@ import operator
 import sickbeard
 
 from sickbeard import db
-from sickbeard import classes, common, helpers, logger, show_name_helpers
+from sickbeard import helpers, logger, show_name_helpers
 from sickbeard import providers
 from sickbeard import search
 from sickbeard import history
 
-from sickbeard.common import *
+from sickbeard.common import DOWNLOADED, SNATCHED, SNATCHED_PROPER, Quality
 
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
@@ -105,7 +105,7 @@ class ProperFinder():
 
             # populate our Proper instance
             if parse_result.air_by_date:
-                curProper.season == -1
+                curProper.season = -1
                 curProper.episode = parse_result.air_date
             else:
                 curProper.season = parse_result.season_number if parse_result.season_number != None else 1
@@ -162,9 +162,9 @@ class ProperFinder():
                 try:
                     t = tvdb_api.Tvdb(**ltvdb_api_parms)
                     epObj = t[curProper.tvdbid].airedOn(curProper.episode)[0]
-                    season = int(epObj["seasonnumber"])
-                    episodes = [int(epObj["episodenumber"])]
-                except tvdb_exceptions.tvdb_episodenotfound, e:
+                    curProper.season = int(epObj["seasonnumber"])
+                    curProper.episodes = [int(epObj["episodenumber"])]
+                except tvdb_exceptions.tvdb_episodenotfound:
                     logger.log(u"Unable to find episode with date "+str(curProper.episode)+" for show "+parse_result.series_name+", skipping", logger.WARNING)
                     continue
 
@@ -232,6 +232,8 @@ class ProperFinder():
 
                 # snatch it
                 downloadResult = search.snatchEpisode(result, SNATCHED_PROPER)
+                
+                return downloadResult
 
     def _genericName(self, name):
         return name.replace(".", " ").replace("-"," ").replace("_"," ").lower()
diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py
index f642a1200abc97bb8b383f4c21f09b5b25abd6c6..30ea49029d6d904aac1f27d5e650e5a397de12f5 100644
--- a/sickbeard/providers/__init__.py
+++ b/sickbeard/providers/__init__.py
@@ -20,7 +20,6 @@ __all__ = ['ezrss',
            'tvtorrents',
            'nzbmatrix',
            'nzbs_org',
-           'tvbinz',
            'nzbsrus',
            'womble',
            'newzbin',
@@ -71,6 +70,11 @@ def getNewznabProviderList(data):
             providerDict[curDefault.name].default = True
             providerDict[curDefault.name].name = curDefault.name
             providerDict[curDefault.name].url = curDefault.url
+        
+        # a 0 in the key spot indicates that no key is needed, so set this on the object
+        if curDefault.key == '0':
+            curDefault.key = ''
+            curDefault.needs_auth = False
 
     return filter(lambda x: x, providerList)
 
@@ -91,7 +95,7 @@ def makeNewznabProvider(configString):
     return newProvider
 
 def getDefaultNewznabProviders():
-    return 'NZB.su|https://nzb.su/||0'
+    return 'Sick Beard Index|http://momo.sickbeard.com/|0|0'
 
 
 def getProviderModule(name):
diff --git a/sickbeard/providers/ezrss.py b/sickbeard/providers/ezrss.py
index f0d2ba9580ea7b28aca5915d360d09db93ec957d..9a645181357c4ada2ed16d66b3f4a97d272a53f4 100644
--- a/sickbeard/providers/ezrss.py
+++ b/sickbeard/providers/ezrss.py
@@ -24,10 +24,11 @@ import xml.etree.cElementTree as etree
 import sickbeard
 import generic
 
-from sickbeard.common import *
+from sickbeard.common import Quality
 from sickbeard import logger
 from sickbeard import tvcache
 from sickbeard.helpers import sanitizeSceneName
+from sickbeard.exceptions import ex
 
 class EZRSSProvider(generic.TorrentProvider):
 
@@ -117,7 +118,7 @@ class EZRSSProvider(generic.TorrentProvider):
             responseSoup = etree.ElementTree(etree.XML(data))
             items = responseSoup.getiterator('item')
         except Exception, e:
-            logger.log(u"Error trying to load EZRSS RSS feed: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error trying to load EZRSS RSS feed: "+ex(e), logger.ERROR)
             logger.log(u"RSS data: "+data, logger.DEBUG)
             return []
         
diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py
index ced160fb7f8667d48213691209b2fd6fc3f0734a..ba28542d8d7c7671d32cdc2245d44b8793de989c 100644
--- a/sickbeard/providers/generic.py
+++ b/sickbeard/providers/generic.py
@@ -18,21 +18,20 @@
 
 
 
-import urllib2
-import os.path
-import sys
 import datetime
-import time
-
-import xml.etree.cElementTree as etree
+import os
+import sys
+import re
+import urllib2
 
 import sickbeard
 
-from sickbeard import helpers, classes, exceptions, logger, db
+from sickbeard import helpers, classes, logger, db
 
-from sickbeard.common import *
+from sickbeard.common import Quality, MULTI_EP_RESULT, SEASON_RESULT
 from sickbeard import tvcache
 from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
 
 from lib.hachoir_parser import createParser
 
@@ -112,7 +111,7 @@ class GenericProvider:
         try:
             result = helpers.getURL(url, headers)
         except (urllib2.HTTPError, IOError), e:
-            logger.log(u"Error loading "+self.name+" URL: " + str(sys.exc_info()) + " - " + str(e), logger.ERROR)
+            logger.log(u"Error loading "+self.name+" URL: " + str(sys.exc_info()) + " - " + ex(e), logger.ERROR)
             return None
 
         return result
@@ -150,7 +149,7 @@ class GenericProvider:
             fileOut.close()
             helpers.chmodAsParent(fileName)
         except IOError, e:
-            logger.log("Unable to save the file: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log("Unable to save the file: "+ex(e), logger.ERROR)
             return False
 
         # as long as it's a valid download then consider it a successful snatch
@@ -164,9 +163,15 @@ class GenericProvider:
         # primitive verification of torrents, just make sure we didn't get a text file or something
         if self.providerType == GenericProvider.TORRENT:
             parser = createParser(file_name)
-            if not parser or parser._getMimeType() != 'application/x-bittorrent':
-                logger.log(u"Result is not a valid torrent file", logger.WARNING)
-                return False
+            if parser:
+                mime_type = parser._getMimeType()
+                try:
+                    parser.stream._input.close()
+                except:
+                    pass
+                if mime_type != 'application/x-bittorrent':
+                    logger.log(u"Result is not a valid torrent file", logger.WARNING)
+                    return False
 
         return True
 
diff --git a/sickbeard/providers/newzbin.py b/sickbeard/providers/newzbin.py
index e3d72ef18ea405aa94349a8c04108fa79bb4110b..f8284a139239e196a389d7333e37f8a261b3d677 100644
--- a/sickbeard/providers/newzbin.py
+++ b/sickbeard/providers/newzbin.py
@@ -16,20 +16,23 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
+import os
 import re
+import sys
 import time
 import urllib
-import sys
 
 import xml.etree.cElementTree as etree
+from datetime import datetime, timedelta
 
 import sickbeard
 import generic
 
 import sickbeard.encodingKludge as ek
-from sickbeard import classes, logger, helpers, exceptions, db, show_name_helpers
+from sickbeard import classes, logger, helpers, exceptions, show_name_helpers
 from sickbeard import tvcache
-from sickbeard.common import *
+from sickbeard.common import Quality
+from sickbeard.exceptions import ex
 
 class NewzbinDownloader(urllib.FancyURLopener):
 
@@ -73,6 +76,8 @@ class NewzbinProvider(generic.NZBProvider):
 
         self.NEWZBIN_NS = 'http://www.newzbin.com/DTD/2007/feeds/report/'
 
+        self.NEWZBIN_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S %Z'
+
     def _report(self, name):
         return '{'+self.NEWZBIN_NS+'}'+name
 
@@ -217,7 +222,7 @@ class NewzbinProvider(generic.NZBProvider):
             logger.log("Done waiting for Newzbin API throttle limit, starting downloads again")
             self.downloadResult(nzb)
         except (urllib.ContentTooShortError, IOError), e:
-            logger.log("Error downloading NZB: " + str(sys.exc_info()) + " - " + str(e), logger.ERROR)
+            logger.log("Error downloading NZB: " + str(sys.exc_info()) + " - " + ex(e), logger.ERROR)
             return False
 
         return True
@@ -228,7 +233,7 @@ class NewzbinProvider(generic.NZBProvider):
         try:
             f = myOpener.openit(url)
         except (urllib.ContentTooShortError, IOError), e:
-            logger.log("Error loading search results: " + str(sys.exc_info()) + " - " + str(e), logger.ERROR)
+            logger.log("Error loading search results: " + str(sys.exc_info()) + " - " + ex(e), logger.ERROR)
             return None
 
         data = f.read()
@@ -273,13 +278,22 @@ class NewzbinProvider(generic.NZBProvider):
             responseSoup = etree.ElementTree(etree.XML(data))
             items = responseSoup.getiterator('item')
         except Exception, e:
-            logger.log("Error trying to load Newzbin RSS feed: "+str(e), logger.ERROR)
+            logger.log("Error trying to load Newzbin RSS feed: "+ex(e), logger.ERROR)
             return []
 
         for cur_item in items:
             title = cur_item.findtext('title')
             if title == 'Feed Error':
                 raise exceptions.AuthException("The feed wouldn't load, probably because of invalid auth info")
+            if sickbeard.USENET_RETENTION is not None:
+                try:
+                    post_date = datetime.strptime(cur_item.findtext('{http://www.newzbin.com/DTD/2007/feeds/report/}postdate'), self.NEWZBIN_DATE_FORMAT)
+                    retention_date = datetime.now() - timedelta(days=sickbeard.USENET_RETENTION)
+                    if post_date < retention_date:
+                        continue
+                except Exception, e:
+                    logger.log("Error parsing date from Newzbin RSS feed: " + str(e), logger.ERROR)
+                    continue
 
             item_list.append(cur_item)
 
diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py
index a1827531d73c9f074a4ba5fc6fd25b0edbd5ff08..6950b05ffe5abf9edbe875ebe6d4ba9724ed7120 100644
--- a/sickbeard/providers/newznab.py
+++ b/sickbeard/providers/newznab.py
@@ -21,6 +21,7 @@
 import urllib
 import datetime
 import re
+import os
 
 import xml.etree.cElementTree as etree
 
@@ -29,11 +30,12 @@ import generic
 
 from sickbeard import classes
 from sickbeard.helpers import sanitizeSceneName
+from sickbeard import encodingKludge as ek
 
 from sickbeard import exceptions
-from sickbeard.common import *
 from sickbeard import logger
 from sickbeard import tvcache
+from sickbeard.exceptions import ex
 
 class NewznabProvider(generic.NZBProvider):
 
@@ -45,6 +47,9 @@ class NewznabProvider(generic.NZBProvider):
 
 		self.url = url
 		self.key = key
+		
+		# if a provider doesn't need an api key then this can be false
+		self.needs_auth = True
 
 		self.enabled = True
 		self.supportsBacklog = True
@@ -55,6 +60,8 @@ class NewznabProvider(generic.NZBProvider):
 		return self.name + '|' + self.url + '|' + self.key + '|' + str(int(self.enabled))
 
 	def imageName(self):
+		if ek.ek(os.path.isfile, ek.ek(os.path.join, sickbeard.PROG_DIR, 'data', 'images', 'providers', self.getID()+'.gif')):
+			return self.getID()+'.gif'
 		return 'newznab.gif'
 
 	def isEnabled(self):
@@ -112,7 +119,6 @@ class NewznabProvider(generic.NZBProvider):
 
 		return [params]
 
-
 	def _doGeneralSearch(self, search_string):
 		return self._doSearch({'q': search_string})
 
@@ -147,7 +153,7 @@ class NewznabProvider(generic.NZBProvider):
 			responseSoup = etree.ElementTree(etree.XML(data))
 			items = responseSoup.getiterator('item')
 		except Exception, e:
-			logger.log(u"Error trying to load "+self.name+" RSS feed: "+str(e).decode('utf-8'), logger.ERROR)
+			logger.log(u"Error trying to load "+self.name+" RSS feed: "+ex(e), logger.ERROR)
 			logger.log(u"RSS data: "+data, logger.DEBUG)
 			return []
 
@@ -236,7 +242,7 @@ class NewznabCache(tvcache.TVCache):
 
 		try:
 			responseSoup = etree.ElementTree(etree.XML(data))
-		except Exception, e:
+		except Exception:
 			return True
 
 		if responseSoup.getroot().tag == 'error':
diff --git a/sickbeard/providers/nzbmatrix.py b/sickbeard/providers/nzbmatrix.py
index f74de9bfed8fd78649e4eaac62bfbd2577c69b12..316f6cad48f1e146476e718fdafc0f41ab3ed723 100644
--- a/sickbeard/providers/nzbmatrix.py
+++ b/sickbeard/providers/nzbmatrix.py
@@ -26,9 +26,9 @@ import xml.etree.cElementTree as etree
 import sickbeard
 import generic
 
-from sickbeard import classes, logger, show_name_helpers, db
+from sickbeard import classes, logger, show_name_helpers
 from sickbeard import tvcache
-from sickbeard.common import *
+from sickbeard.exceptions import ex
 
 class NZBMatrixProvider(generic.NZBProvider):
 
@@ -94,7 +94,7 @@ class NZBMatrixProvider(generic.NZBProvider):
             responseSoup = etree.ElementTree(etree.XML(searchResult))
             items = responseSoup.getiterator('item')
         except Exception, e:
-            logger.log(u"Error trying to load NZBMatrix RSS feed: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error trying to load NZBMatrix RSS feed: "+ex(e), logger.ERROR)
             return []
 
         results = []
diff --git a/sickbeard/providers/nzbs_org.py b/sickbeard/providers/nzbs_org.py
index b390c2eed178defb7cf6ebf6360ab6d26757ecfe..7e5b5ebe64f79eb7176af1cb5e6e4efc3c65dc4a 100644
--- a/sickbeard/providers/nzbs_org.py
+++ b/sickbeard/providers/nzbs_org.py
@@ -18,9 +18,10 @@
 
 
 
-import urllib
 import datetime
+import re
 import time
+import urllib
 
 import xml.etree.cElementTree as etree
 
@@ -29,9 +30,9 @@ import generic
 
 from sickbeard import classes, show_name_helpers
 
-from sickbeard import exceptions, logger, db
-from sickbeard.common import *
+from sickbeard import exceptions, logger
 from sickbeard import tvcache
+from sickbeard.exceptions import ex
 
 class NZBsProvider(generic.NZBProvider):
 
@@ -87,7 +88,7 @@ class NZBsProvider(generic.NZBProvider):
 			responseSoup = etree.ElementTree(etree.XML(data))
 			items = responseSoup.getiterator('item')
 		except Exception, e:
-			logger.log(u"Error trying to load NZBs.org RSS feed: "+str(e).decode('utf-8'), logger.ERROR)
+			logger.log(u"Error trying to load NZBs.org RSS feed: "+ex(e), logger.ERROR)
 			return []
 
 		results = []
diff --git a/sickbeard/providers/nzbsrus.py b/sickbeard/providers/nzbsrus.py
index 26c5eb712aae0f0b7a0a397dfbfea1a0acfe0ee7..c90365f30768bd1d576459f6f440f772664ea85c 100644
--- a/sickbeard/providers/nzbsrus.py
+++ b/sickbeard/providers/nzbsrus.py
@@ -24,7 +24,6 @@ import sickbeard
 
 from sickbeard import exceptions, logger
 
-from sickbeard.common import *
 from sickbeard import tvcache
 
 import generic
diff --git a/sickbeard/providers/tvbinz.py b/sickbeard/providers/tvbinz.py
deleted file mode 100644
index 6cb035851d01996a9db4d5fff56ddfb0d180073b..0000000000000000000000000000000000000000
--- a/sickbeard/providers/tvbinz.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# Author: Nic Wolfe <nic@wolfeden.ca>
-# 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 <http://www.gnu.org/licenses/>.
-
-from __future__ import with_statement
-
-import urllib
-import urllib2
-import sys
-
-
-import sickbeard
-import generic
-
-from sickbeard import helpers, classes, exceptions, logger
-from sickbeard import tvcache
-
-from sickbeard.common import *
-
-class TVBinzProvider(generic.NZBProvider):
-
-	def __init__(self):
-
-		generic.NZBProvider.__init__(self, "TVBinz")
-
-		self.cache = TVBinzCache(self)
-
-		self.url = 'https://www.tvbinz.net/'
-
-	def isEnabled(self):
-		return sickbeard.TVBINZ
-
-	def _checkAuth(self):
-		if sickbeard.TVBINZ_UID in (None, "") or sickbeard.TVBINZ_HASH in (None, "") or sickbeard.TVBINZ_AUTH in (None, ""):
-			raise exceptions.AuthException("TVBinz authentication details are empty, check your config")
-
-	def getURL (self, url):
-
-		cookie_header = ("Cookie", "uid=" + sickbeard.TVBINZ_UID + ";hash=" + sickbeard.TVBINZ_HASH + ";auth=" + sickbeard.TVBINZ_AUTH)
-
-		result = generic.NZBProvider.getURL(self, url, [cookie_header])
-
-		return result
-
-
-
-class TVBinzCache(tvcache.TVCache):
-
-	def __init__(self, provider):
-
-		tvcache.TVCache.__init__(self, provider)
-
-		# only poll TVBinz every 10 minutes max
-		self.minTime = 10
-
-	def _getRSSData(self):
-		# get all records since the last timestamp
-		url = self.provider.url + "rss.php?"
-
-		urlArgs = {'normalize': 1012,
-				   'n': 100,
-				   'maxage': sickbeard.USENET_RETENTION,
-				   'seriesinfo': 1,
-				   'nodupes': 1,
-				   'sets': 'none',
-                   'addauth': '1'}
-
-		url += urllib.urlencode(urlArgs)
-
-		logger.log(u"TVBinz cache update URL: "+ url, logger.DEBUG)
-
-		data = self.provider.getURL(url)
-
-		return data
-
-	def _parseItem(self, item):
-
-		if item.findtext('title') != None and item.findtext('title') == "You must be logged in to view this feed":
-			raise exceptions.AuthException("TVBinz authentication details are incorrect, check your config")
-
-		if item.findtext('title') == None or item.findtext('link') == None:
-			logger.log(u"The XML returned from the TVBinz RSS feed is incomplete, this result is unusable: "+str(item), logger.ERROR)
-			return
-
-		title = item.findtext('title')
-		url = item.findtext('link').replace('&amp;', '&')
-
-		sInfo = item.find('{http://tvbinz.net/rss/tvb/}seriesInfo')
-		if sInfo == None:
-			logger.log(u"No series info, this is some kind of non-standard release, ignoring it", logger.DEBUG)
-			return
-
-		logger.log(u"Adding item from RSS to cache: "+title, logger.DEBUG)
-
-		quality = Quality.nameQuality(title)
-
-		if sInfo.findtext('{http://tvbinz.net/rss/tvb/}tvrID') == None:
-			tvrid = 0
-		else:
-			tvrid = int(sInfo.findtext('{http://tvbinz.net/rss/tvb/}tvrID'))
-
-		# since TVBinz normalizes the scene names it's more reliable to parse the episodes out myself
-		# than to rely on it, because it doesn't support multi-episode numbers in the feed
-		self._addCacheEntry(title, url, tvrage_id=tvrid, quality=quality)
-
-provider = TVBinzProvider()
\ No newline at end of file
diff --git a/sickbeard/providers/tvtorrents.py b/sickbeard/providers/tvtorrents.py
index 5281a9a37531ff06fc7d599873f3419225095696..fa5e24a2d0106e39588b518681367c591ae897b6 100644
--- a/sickbeard/providers/tvtorrents.py
+++ b/sickbeard/providers/tvtorrents.py
@@ -16,14 +16,11 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import urllib
-
 import xml.etree.cElementTree as etree
 
 import sickbeard
 import generic
 
-from sickbeard.common import *
 from sickbeard import logger
 from sickbeard import tvcache
 
diff --git a/sickbeard/providers/womble.py b/sickbeard/providers/womble.py
index b65b067e8349a4e2a08d9ae9f0e3e0ab2e377059..0591b58fdc8c65cca2ca35c90ab51c82af156565 100644
--- a/sickbeard/providers/womble.py
+++ b/sickbeard/providers/womble.py
@@ -18,13 +18,10 @@
 
 
 
-import urllib
-
 import sickbeard
 
-from sickbeard import exceptions, logger
+from sickbeard import logger
 
-from sickbeard.common import *
 from sickbeard import tvcache
 
 import generic
diff --git a/sickbeard/sab.py b/sickbeard/sab.py
index 77fa475189c117020647d13dba90148cf99f947d..80515ed9038ffb7af286c686c2d7b774035cc0c6 100644
--- a/sickbeard/sab.py
+++ b/sickbeard/sab.py
@@ -26,8 +26,9 @@ import sickbeard
 from lib import MultipartPostHandler
 import urllib2, cookielib
 
-from sickbeard.common import *
-from sickbeard import logger, classes
+from sickbeard.common import USER_AGENT
+from sickbeard import logger
+from sickbeard.exceptions import ex
 
 def sendNZB(nzb):
 
@@ -88,11 +89,11 @@ def sendNZB(nzb):
             f = opener.open(req)
 
     except (EOFError, IOError), e:
-        logger.log(u"Unable to connect to SAB: "+str(e), logger.ERROR)
+        logger.log(u"Unable to connect to SAB: "+ex(e), logger.ERROR)
         return False
 
     except httplib.InvalidURL, e:
-        logger.log(u"Invalid SAB host, check your config: "+str(e).decode('utf-8'), logger.ERROR)
+        logger.log(u"Invalid SAB host, check your config: "+ex(e), logger.ERROR)
         return False
 
     if f == None:
@@ -102,7 +103,7 @@ def sendNZB(nzb):
     try:
         result = f.readlines()
     except Exception, e:
-        logger.log(u"Error trying to get result from SAB, NZB not sent: " + str(e), logger.ERROR)
+        logger.log(u"Error trying to get result from SAB, NZB not sent: " + ex(e), logger.ERROR)
         return False
 
     if len(result) == 0:
diff --git a/sickbeard/scene_exceptions.py b/sickbeard/scene_exceptions.py
index 999f869dd176c03528bffe81c7cf2373a7b2cd47..4fa8881654d8d97e6292be2ef31085f1b441c2a2 100644
--- a/sickbeard/scene_exceptions.py
+++ b/sickbeard/scene_exceptions.py
@@ -72,7 +72,7 @@ def retrieve_exceptions():
     
     # each exception is on one line with the format tvdb_id: 'show name 1', 'show name 2', etc
     for cur_line in open_url.readlines():
-        tvdb_id, sep, aliases = cur_line.partition(':')
+        tvdb_id, sep, aliases = cur_line.partition(':') #@UnusedVariable
         
         if not aliases:
             continue
@@ -85,12 +85,21 @@ def retrieve_exceptions():
         exception_dict[tvdb_id] = alias_list
 
     myDB = db.DBConnection("cache.db")
-    myDB.action("DELETE FROM scene_exceptions WHERE 1=1")
+
+    changed_exceptions = False
 
     # write all the exceptions we got off the net into the database
     for cur_tvdb_id in exception_dict:
+
+        # get a list of the existing exceptions for this ID
+        existing_exceptions = [x["show_name"] for x in myDB.select("SELECT * FROM scene_exceptions WHERE tvdb_id = ?", [cur_tvdb_id])]
+        
         for cur_exception in exception_dict[cur_tvdb_id]:
-            myDB.action("INSERT INTO scene_exceptions (tvdb_id, show_name) VALUES (?,?)", [cur_tvdb_id, cur_exception])
+            # if this exception isn't already in the DB then add it
+            if cur_exception not in existing_exceptions:
+                myDB.action("INSERT INTO scene_exceptions (tvdb_id, show_name) VALUES (?,?)", [cur_tvdb_id, cur_exception])
+                changed_exceptions = True
 
     # since this could invalidate the results of the cache we clear it out after updating
-    name_cache.clearCache()
\ No newline at end of file
+    if changed_exceptions:
+        name_cache.clearCache()
\ No newline at end of file
diff --git a/sickbeard/scheduler.py b/sickbeard/scheduler.py
index 55b6deefea3dbee7731ae724e6ef1f56dbea7ab2..6b31a71297998eb144b8a20523a6b2d6347f1af4 100644
--- a/sickbeard/scheduler.py
+++ b/sickbeard/scheduler.py
@@ -21,9 +21,8 @@ import time
 import threading
 import traceback
 
-from lib.tvdb_api import tvdb_exceptions
-
 from sickbeard import logger
+from sickbeard.exceptions import ex
 
 class Scheduler:
 
@@ -71,7 +70,7 @@ class Scheduler:
                         logger.log(u"Starting new thread: "+self.threadName, logger.DEBUG)
                     self.action.run()
                 except Exception, e:
-                    logger.log(u"Exception generated in thread "+self.threadName+": " + str(e), logger.ERROR)
+                    logger.log(u"Exception generated in thread "+self.threadName+": " + ex(e), logger.ERROR)
                     logger.log(traceback.format_exc(), logger.DEBUG)
 
             if self.abort:
diff --git a/sickbeard/search.py b/sickbeard/search.py
index f4a1726ee13ea3d713b8537a23a0f39de20d0ad2..0175e5801c3cfb4482e736014e4d3c377443ee6b 100644
--- a/sickbeard/search.py
+++ b/sickbeard/search.py
@@ -18,11 +18,12 @@
 
 from __future__ import with_statement
 
+import os
 import traceback
 
 import sickbeard
 
-from common import *
+from common import SNATCHED, Quality, SEASON_RESULT, MULTI_EP_RESULT
 
 from sickbeard import logger, db, show_name_helpers, exceptions, helpers
 from sickbeard import sab
@@ -30,10 +31,9 @@ from sickbeard import nzbget
 from sickbeard import history
 from sickbeard import notifiers
 from sickbeard import nzbSplitter
-
+from sickbeard import ui
 from sickbeard import encodingKludge as ek
-
-from sickbeard.providers import *
+from sickbeard.exceptions import ex
 from sickbeard import providers
 
 def _downloadResult(result):
@@ -74,7 +74,7 @@ def _downloadResult(result):
             fileOut.close()
             helpers.chmodAsParent(fileName)
         except IOError, e:
-            logger.log(u"Error trying to save NZB to black hole: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error trying to save NZB to black hole: "+ex(e), logger.ERROR)
             newResult = False
 
     elif resProvider.providerType == "torrent":
@@ -84,6 +84,9 @@ def _downloadResult(result):
         logger.log(u"Invalid provider type - this is a coding error, report it please", logger.ERROR)
         return False
 
+    if newResult:
+        ui.notifications.message('Episode <b>%s</b> snatched from <b>%s</b>' % (result.name, resProvider.name))
+
     return newResult
 
 def snatchEpisode(result, endStatus=SNATCHED):
@@ -151,10 +154,10 @@ def searchForNeededEpisodes():
         try:
             curFoundResults = curProvider.searchRSS()
         except exceptions.AuthException, e:
-            logger.log(u"Authentication error: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Authentication error: "+ex(e), logger.ERROR)
             continue
         except Exception, e:
-            logger.log(u"Error while searching "+curProvider.name+", skipping: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error while searching "+curProvider.name+", skipping: "+ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
             continue
 
@@ -232,10 +235,10 @@ def findEpisode(episode, manualSearch=False):
         try:
             curFoundResults = curProvider.findEpisode(episode, manualSearch=manualSearch)
         except exceptions.AuthException, e:
-            logger.log(u"Authentication error: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Authentication error: "+ex(e), logger.ERROR)
             continue
         except Exception, e:
-            logger.log(u"Error while searching "+curProvider.name+", skipping: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error while searching "+curProvider.name+", skipping: "+ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
             continue
 
@@ -281,10 +284,10 @@ def findSeason(show, season):
                     foundResults[curEp] = curResults[curEp]
 
         except exceptions.AuthException, e:
-            logger.log(u"Authentication error: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Authentication error: "+ex(e), logger.ERROR)
             continue
         except Exception, e:
-            logger.log(u"Error while searching "+curProvider.name+", skipping: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error while searching "+curProvider.name+", skipping: "+ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
             continue
 
diff --git a/sickbeard/searchBacklog.py b/sickbeard/searchBacklog.py
index 11e2bbf83a552b49dabdc16738bb49e4318d35aa..ce92424a063882c3516ad39f8f367f6a38839da0 100644
--- a/sickbeard/searchBacklog.py
+++ b/sickbeard/searchBacklog.py
@@ -20,13 +20,14 @@ from __future__ import with_statement
 
 import datetime
 import threading
-import time
 
-from sickbeard import db, exceptions, helpers, search, scheduler
+import sickbeard
+
+from sickbeard import db, scheduler
 from sickbeard import search_queue
 from sickbeard import logger
 from sickbeard import ui
-from sickbeard.common import *
+#from sickbeard.common import *
 
 class BacklogSearchScheduler(scheduler.Scheduler):
 
@@ -90,11 +91,11 @@ class BacklogSearcher:
         self.amActive = True
         self.amPaused = False
 
-        myDB = db.DBConnection()
-        numSeasonResults = myDB.select("SELECT DISTINCT(season), showid FROM tv_episodes ep, tv_shows show WHERE season != 0 AND ep.showid = show.tvdb_id AND show.paused = 0 AND ep.airdate > ?", [fromDate.toordinal()])
+        #myDB = db.DBConnection()
+        #numSeasonResults = myDB.select("SELECT DISTINCT(season), showid FROM tv_episodes ep, tv_shows show WHERE season != 0 AND ep.showid = show.tvdb_id AND show.paused = 0 AND ep.airdate > ?", [fromDate.toordinal()])
 
         # get separate lists of the season/date shows
-        season_shows = [x for x in show_list if not x.air_by_date]
+        #season_shows = [x for x in show_list if not x.air_by_date]
         air_by_date_shows = [x for x in show_list if x.air_by_date]
 
         # figure out how many segments of air by date shows we're going to do
@@ -104,8 +105,8 @@ class BacklogSearcher:
 
         logger.log(u"Air-by-date segments: "+str(air_by_date_segments), logger.DEBUG)
 
-        totalSeasons = float(len(numSeasonResults) + len(air_by_date_segments))
-        numSeasonsDone = 0.0
+        #totalSeasons = float(len(numSeasonResults) + len(air_by_date_segments))
+        #numSeasonsDone = 0.0
 
         # go through non air-by-date shows and see if they need any episodes
         for curShow in show_list:
@@ -127,7 +128,7 @@ class BacklogSearcher:
                 if not backlog_queue_item.wantSeason:
                     logger.log(u"Nothing in season "+str(cur_segment)+" needs to be downloaded, skipping this season", logger.DEBUG)
                 else:
-                    sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item)
+                    sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item)  #@UndefinedVariable
 
         # don't consider this an actual backlog search if we only did recent eps
         # or if we only did certain shows
diff --git a/sickbeard/searchCurrent.py b/sickbeard/searchCurrent.py
index f28019039def7f4c638f3c13d8612b1afe5e7f9c..ff3392a759e0fb36a56d2491867d08a566eae19c 100644
--- a/sickbeard/searchCurrent.py
+++ b/sickbeard/searchCurrent.py
@@ -18,15 +18,11 @@
 
 from __future__ import with_statement
 
-from sickbeard import common, db, exceptions, helpers, search
+import sickbeard
+
 from sickbeard import search_queue
-from sickbeard import logger
-from sickbeard import ui
-from sickbeard.common import *
 
-import datetime
 import threading
-import time
 
 class CurrentSearcher():
 
@@ -37,4 +33,4 @@ class CurrentSearcher():
 
     def run(self):
         search_queue_item = search_queue.RSSSearchQueueItem()
-        sickbeard.searchQueueScheduler.action.add_item(search_queue_item)
+        sickbeard.searchQueueScheduler.action.add_item(search_queue_item) #@UndefinedVariable
diff --git a/sickbeard/search_queue.py b/sickbeard/search_queue.py
index da136ade643be0334740c89a9ed866b8882c8167..148aa1979e984cddaf683e108fad20cf8e785c97 100644
--- a/sickbeard/search_queue.py
+++ b/sickbeard/search_queue.py
@@ -25,9 +25,11 @@ import sickbeard
 from sickbeard import db, logger, common, exceptions, helpers
 from sickbeard import generic_queue
 from sickbeard import search
+from sickbeard import ui
 
 BACKLOG_SEARCH = 10
 RSS_SEARCH = 20
+MANUAL_SEARCH = 30
 
 class SearchQueue(generic_queue.GenericQueue):
     
@@ -41,6 +43,12 @@ class SearchQueue(generic_queue.GenericQueue):
                 return True
         return False
 
+    def is_ep_in_queue(self, ep_obj):
+        for cur_item in self.queue:
+            if isinstance(cur_item, ManualSearchQueueItem) and cur_item.ep_obj == ep_obj:
+                return True
+        return False
+
     def pause_backlog(self):
         self.min_priority = generic_queue.QueuePriorities.HIGH
 
@@ -58,16 +66,58 @@ class SearchQueue(generic_queue.GenericQueue):
         return False
 
     def add_item(self, item):
+        if isinstance(item, RSSSearchQueueItem):
+            generic_queue.GenericQueue.add_item(self, item)
         # don't do duplicates
-        if isinstance(item, RSSSearchQueueItem) or not self.is_in_queue(item.show, item.segment):
+        elif isinstance(item, BacklogQueueItem) and not self.is_in_queue(item.show, item.segment):
+            generic_queue.GenericQueue.add_item(self, item)
+        elif isinstance(item, ManualSearchQueueItem) and not self.is_ep_in_queue(item.ep_obj):
             generic_queue.GenericQueue.add_item(self, item)
         else:
             logger.log(u"Not adding item, it's already in the queue", logger.DEBUG)
 
+class ManualSearchQueueItem(generic_queue.QueueItem):
+    def __init__(self, ep_obj):
+        generic_queue.QueueItem.__init__(self, 'Manual Search', MANUAL_SEARCH)
+        self.priority = generic_queue.QueuePriorities.HIGH
+        
+        self.ep_obj = ep_obj
+        
+        self.success = None
+
+    def execute(self):
+        generic_queue.QueueItem.execute(self)
+
+        logger.log("Searching for download for " + self.ep_obj.prettyName(True))
+
+        foundEpisode = search.findEpisode(self.ep_obj, manualSearch=True)
+
+        if not foundEpisode:
+            ui.notifications.message('No downloads were found', "Couldn't find a download for <i>%s</i>" % self.ep_obj.prettyName(True))
+            logger.log(u"Unable to find a download for "+self.ep_obj.prettyName(True))
+
+        else:
+
+            # just use the first result for now
+            logger.log(u"Downloading episode from " + foundEpisode.url)
+            result = search.snatchEpisode(foundEpisode)
+            providerModule = foundEpisode.provider
+            if not result:
+                ui.notifications.error('Error while attempting to snatch '+foundEpisode.name+', check your logs')
+            elif providerModule == None:
+                ui.notifications.error('Provider is configured incorrectly, unable to download')
+
+        self.success = foundEpisode
+
+    def finish(self):
+        # don't let this linger if something goes wrong
+        if self.success == None:
+            self.success = False
+        generic_queue.QueueItem.finish(self)
+
 class RSSSearchQueueItem(generic_queue.QueueItem):
     def __init__(self):
         generic_queue.QueueItem.__init__(self, 'RSS Search', RSS_SEARCH)
-        self.priority = generic_queue.QueuePriorities.HIGH
 
     def execute(self):
         generic_queue.QueueItem.execute(self)
@@ -119,6 +169,7 @@ class RSSSearchQueueItem(generic_queue.QueueItem):
 class BacklogQueueItem(generic_queue.QueueItem):
     def __init__(self, show, segment):
         generic_queue.QueueItem.__init__(self, 'Backlog', BACKLOG_SEARCH)
+        self.priority = generic_queue.QueuePriorities.LOW
         self.thread_name = 'BACKLOG-'+str(show.tvdbid)
         
         self.show = show
@@ -144,7 +195,7 @@ class BacklogQueueItem(generic_queue.QueueItem):
             statusResults = myDB.select("SELECT status FROM tv_episodes WHERE showid = ? AND airdate >= ? AND airdate <= ?",
                                         [self.show.tvdbid, min_date.toordinal(), max_date.toordinal()])
             
-        anyQualities, bestQualities = common.Quality.splitQuality(self.show.quality)
+        anyQualities, bestQualities = common.Quality.splitQuality(self.show.quality) #@UnusedVariable
         self.wantSeason = self._need_any_episodes(statusResults, bestQualities)
 
     def execute(self):
diff --git a/sickbeard/showUpdater.py b/sickbeard/showUpdater.py
index 5ffd5008874a32903bd5bf433ac8531366072887..bb0830ee3696d2e8c80d0e394a46033132ecb468 100644
--- a/sickbeard/showUpdater.py
+++ b/sickbeard/showUpdater.py
@@ -18,11 +18,12 @@
 
 import datetime
 
-from sickbeard.common import *
+import sickbeard
 
 from sickbeard import logger
 from sickbeard import exceptions
 from sickbeard import ui
+from sickbeard.exceptions import ex
 
 class ShowUpdater():
 
@@ -51,15 +52,15 @@ class ShowUpdater():
             try:
 
                 if curShow.status != "Ended":
-                    curQueueItem = sickbeard.showQueueScheduler.action.updateShow(curShow, True)
+                    curQueueItem = sickbeard.showQueueScheduler.action.updateShow(curShow, True) #@UndefinedVariable
                 else:
                     #TODO: maybe I should still update specials?
                     logger.log(u"Not updating episodes for show "+curShow.name+" because it's marked as ended.", logger.DEBUG)
-                    curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(curShow, True)
+                    curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(curShow, True) #@UndefinedVariable
 
                 piList.append(curQueueItem)
 
             except (exceptions.CantUpdateException, exceptions.CantRefreshException), e:
-                logger.log(u"Automatic update failed: " + str(e), logger.ERROR)
+                logger.log(u"Automatic update failed: " + ex(e), logger.ERROR)
 
         ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator("Daily Update", piList))
diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py
index 7b7944c70210270eef883cb5b5855a44b685ec06..dc244dad15f98ce5b31b9fcc0bc895c56beeca03 100644
--- a/sickbeard/show_name_helpers.py
+++ b/sickbeard/show_name_helpers.py
@@ -16,6 +16,8 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
+import sickbeard
+
 from sickbeard.common import countryList
 from sickbeard.helpers import sanitizeSceneName
 from sickbeard.scene_exceptions import get_scene_exceptions
@@ -24,14 +26,12 @@ from sickbeard import db
 
 import re
 import datetime
-import urllib
 
 from name_parser.parser import NameParser, InvalidNameException
 
-resultFilters = ("sub(pack|s|bed)", "nlsub(bed|s)?", "swesub(bed)?",
+resultFilters = ["sub(pack|s|bed)", "nlsub(bed|s)?", "swesub(bed)?",
                  "(dir|sample|nfo)fix", "sample", "(dvd)?extras", 
-                 "dub(bed)?", "german", "french", "core2hd",
-                 "dutch", "swedish")
+                 "dub(bed)?"]
 
 def filterBadReleases(name):
     """
@@ -50,13 +50,23 @@ def filterBadReleases(name):
         logger.log(u"Unable to parse the filename "+name+" into a valid episode", logger.WARNING)
         return False
 
+    # use the extra info and the scene group to filter against
+    check_string = ''
+    if parse_result.extra_info:
+        check_string = parse_result.extra_info
+    if parse_result.release_group:
+        if check_string:
+            check_string = check_string + '-' + parse_result.release_group
+        else:
+            check_string = parse_result.release_group 
+
     # if there's no info after the season info then assume it's fine
-    if not parse_result.extra_info:
+    if not check_string:
         return True
 
     # if any of the bad strings are in the name then say no
-    for x in resultFilters:
-        if re.search('(^|[\W_])'+x+'($|[\W_])', parse_result.extra_info, re.I):
+    for x in resultFilters + sickbeard.IGNORE_WORDS.split(','):
+        if re.search('(^|[\W_])'+x+'($|[\W_])', check_string, re.I):
             logger.log(u"Invalid scene release: "+name+" contains "+x+", ignoring it", logger.DEBUG)
             return False
 
diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py
index 1ff65aa50043c459fe631e90037b61ecdfd15001..cbfa169fda94374bc1a16d305525d420bd979b30 100644
--- a/sickbeard/show_queue.py
+++ b/sickbeard/show_queue.py
@@ -18,18 +18,19 @@
 
 from __future__ import with_statement
 
-import os.path
-import threading
 import traceback
 
+import sickbeard
+
 from lib.tvdb_api import tvdb_exceptions, tvdb_api
 
-from sickbeard.common import *
+from sickbeard.common import SKIPPED, WANTED
 
 from sickbeard.tv import TVShow
-from sickbeard import exceptions, helpers, logger, ui, db
+from sickbeard import exceptions, logger, ui, db
 from sickbeard import generic_queue
 from sickbeard import name_cache
+from sickbeard.exceptions import ex
 
 class ShowQueue(generic_queue.GenericQueue):
 
@@ -87,7 +88,7 @@ class ShowQueue(generic_queue.GenericQueue):
         else:
             queueItemObj = QueueItemForceUpdate(show)
 
-        self.queue.append(queueItemObj)
+        self.add_item(queueItemObj)
 
         return queueItemObj
 
@@ -102,7 +103,7 @@ class ShowQueue(generic_queue.GenericQueue):
 
         queueItemObj = QueueItemRefresh(show)
         
-        self.queue.append(queueItemObj)
+        self.add_item(queueItemObj)
 
         return queueItemObj
 
@@ -110,13 +111,14 @@ class ShowQueue(generic_queue.GenericQueue):
 
         queueItemObj = QueueItemRename(show)
 
-        self.queue.append(queueItemObj)
+        self.add_item(queueItemObj)
 
         return queueItemObj
 
     def addShow(self, tvdb_id, showDir, default_status=None, quality=None, season_folders=None, lang="en"):
         queueItemObj = QueueItemAdd(tvdb_id, showDir, default_status, quality, season_folders, lang)
-        self.queue.append(queueItemObj)
+        
+        self.add_item(queueItemObj)
 
         return queueItemObj
 
@@ -149,7 +151,7 @@ class ShowQueueItem(generic_queue.QueueItem):
         self.show = show
     
     def isInQueue(self):
-        return self in sickbeard.showQueueScheduler.action.queue+[sickbeard.showQueueScheduler.action.currentItem]
+        return self in sickbeard.showQueueScheduler.action.queue+[sickbeard.showQueueScheduler.action.currentItem] #@UndefinedVariable
 
     def _getName(self):
         return str(self.show.tvdbid)
@@ -220,12 +222,12 @@ class QueueItemAdd(ShowQueueItem):
                 # this usually only happens if they have an NFO in their show dir which gave us a TVDB ID that has no
                 # proper english version of the show
                 if not s or not s['seriesname']:
-                    ui.flash.error("Unable to add show", "Show in "+self.showDir+" has no name on TVDB, probably the wrong language. Delete .nfo and add manually in the correct language.")
+                    ui.notifications.error("Unable to add show", "Show in "+self.showDir+" has no name on TVDB, probably the wrong language. Delete .nfo and add manually in the correct language.")
                     self._finishEarly()
                     return
             except tvdb_exceptions.tvdb_exception, e:
-                logger.log(u"Error contacting TVDB: "+str(e), logger.ERROR)
-                ui.flash.error("Unable to add show", "Unable to look up the show in "+self.showDir+" on TVDB, not using the NFO. Delete .nfo and add manually in the correct language.")
+                logger.log(u"Error contacting TVDB: "+ex(e), logger.ERROR)
+                ui.notifications.error("Unable to add show", "Unable to look up the show in "+self.showDir+" on TVDB, not using the NFO. Delete .nfo and add manually in the correct language.")
                 self._finishEarly()
                 return
 
@@ -248,22 +250,22 @@ class QueueItemAdd(ShowQueueItem):
                 self.show.air_by_date = 1
 
         except tvdb_exceptions.tvdb_exception, e:
-            logger.log(u"Unable to add show due to an error with TVDB: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to add show due to an error with TVDB: "+ex(e), logger.ERROR)
             if self.show:
-                ui.flash.error("Unable to add "+str(self.show.name)+" due to an error with TVDB")
+                ui.notifications.error("Unable to add "+str(self.show.name)+" due to an error with TVDB")
             else:
-                ui.flash.error("Unable to add show due to an error with TVDB")
+                ui.notifications.error("Unable to add show due to an error with TVDB")
             self._finishEarly()
             return
 
         except exceptions.MultipleShowObjectsException:
             logger.log(u"The show in " + self.showDir + " is already in your show list, skipping", logger.ERROR)
-            ui.flash.error('Show skipped', "The show in " + self.showDir + " is already in your show list")
+            ui.notifications.error('Show skipped', "The show in " + self.showDir + " is already in your show list")
             self._finishEarly()
             return
 
         except Exception, e:
-            logger.log(u"Error trying to add show: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error trying to add show: "+ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
             self._finishEarly()
             raise
@@ -274,7 +276,7 @@ class QueueItemAdd(ShowQueueItem):
         try:
             self.show.loadEpisodesFromDir()
         except Exception, e:
-            logger.log(u"Error searching dir for episodes: " + str(e), logger.ERROR)
+            logger.log(u"Error searching dir for episodes: " + ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
 
         try:
@@ -285,13 +287,13 @@ class QueueItemAdd(ShowQueueItem):
             self.show.populateCache()
             
         except Exception, e:
-            logger.log(u"Error with TVDB, not creating episode list: " + str(e), logger.ERROR)
+            logger.log(u"Error with TVDB, not creating episode list: " + ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
 
         try:
             self.show.saveToDB()
         except Exception, e:
-            logger.log(u"Error saving the episode to the database: " + str(e), logger.ERROR)
+            logger.log(u"Error saving the episode to the database: " + ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
 
         # if they gave a custom status then change all the eps to it
@@ -303,7 +305,7 @@ class QueueItemAdd(ShowQueueItem):
         # if they started with WANTED eps then run the backlog
         if self.default_status == WANTED:
             logger.log(u"Launching backlog for this show since its episodes are WANTED")
-            sickbeard.backlogSearchScheduler.action.searchBacklog([self.show])
+            sickbeard.backlogSearchScheduler.action.searchBacklog([self.show]) #@UndefinedVariable
 
         self.show.flushEpisodes()
 
@@ -361,7 +363,11 @@ class QueueItemUpdate(ShowQueueItem):
         logger.log(u"Beginning update of "+self.show.name)
 
         logger.log(u"Retrieving show info from TVDB", logger.DEBUG)
-        self.show.loadFromTVDB(cache=not self.force)
+        try:
+            self.show.loadFromTVDB(cache=not self.force)
+        except tvdb_exceptions.tvdb_error, e:
+            logger.log(u"Unable to contact TVDB, aborting: "+ex(e), logger.WARNING)
+            return
 
         # get episode list from DB
         logger.log(u"Loading all episodes from the database", logger.DEBUG)
@@ -372,7 +378,7 @@ class QueueItemUpdate(ShowQueueItem):
         try:
             TVDBEpList = self.show.loadEpisodesFromTVDB(cache=not self.force)
         except tvdb_exceptions.tvdb_exception, e:
-            logger.log(u"Unable to get info from TVDB, the show info will not be refreshed: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to get info from TVDB, the show info will not be refreshed: "+ex(e), logger.ERROR)
             TVDBEpList = None
 
         if TVDBEpList == None:
@@ -404,7 +410,7 @@ class QueueItemUpdate(ShowQueueItem):
             if self.show.tvrid == 0:
                 self.show.setTVRID()
 
-        sickbeard.showQueueScheduler.action.refreshShow(self.show, True)
+        sickbeard.showQueueScheduler.action.refreshShow(self.show, True) #@UndefinedVariable
 
 class QueueItemForceUpdate(QueueItemUpdate):
     def __init__(self, show=None):
diff --git a/sickbeard/tv.py b/sickbeard/tv.py
index 885b7428191b62bc43451fad59428c497c4f3639..49d06d6ca4988d03d0b96e643319849ac72c905e 100644
--- a/sickbeard/tv.py
+++ b/sickbeard/tv.py
@@ -34,6 +34,7 @@ from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
 from sickbeard import db
 from sickbeard import helpers, exceptions, logger
+from sickbeard.exceptions import ex
 from sickbeard import tvrage
 from sickbeard import config
 from sickbeard import image_cache
@@ -41,7 +42,8 @@ from sickbeard import postProcessor
 
 from sickbeard import encodingKludge as ek
 
-from common import *
+from common import Quality, Overview
+from common import DOWNLOADED, SNATCHED, SNATCHED_PROPER, ARCHIVED, IGNORED, UNAIRED, WANTED, SKIPPED, UNKNOWN
 
 class TVShow(object):
 
@@ -70,7 +72,7 @@ class TVShow(object):
         self._isDirGood = False
 
         self.episodes = {}
-
+        
         otherShow = helpers.findCertainShow(sickbeard.showList, self.tvdbid)
         if otherShow != None:
             raise exceptions.MultipleShowObjectsException("Can't create a show if it already exists")
@@ -198,7 +200,7 @@ class TVShow(object):
             try:
                 curEpisode = self.makeEpFromFile(os.path.join(self._location, mediaFile))
             except (exceptions.ShowNotFoundException, exceptions.EpisodeNotFoundException), e:
-                logger.log(u"Episode "+mediaFile+" returned an exception: "+str(e).decode('utf-8'), logger.ERROR)
+                logger.log(u"Episode "+mediaFile+" returned an exception: "+ex(e), logger.ERROR)
             except exceptions.EpisodeDeletedException:
                 logger.log(u"The episode deleted itself when I tried making an object for it", logger.DEBUG)
 
@@ -313,10 +315,10 @@ class TVShow(object):
 
         try:
             # load the tvrage object, it will set the ID in its constructor if possible
-            tvr = tvrage.TVRage(self)
+            tvrage.TVRage(self)
             self.saveToDB()
         except exceptions.TVRageException, e:
-            logger.log(u"Couldn't get TVRage ID because we're unable to sync TVDB and TVRage: "+str(e).decode('utf-8'), logger.DEBUG)
+            logger.log(u"Couldn't get TVRage ID because we're unable to sync TVDB and TVRage: "+ex(e), logger.DEBUG)
             return
 
     def getImages(self, fanart=None, poster=None):
@@ -345,7 +347,7 @@ class TVShow(object):
 
             # make an episode out of it
         except exceptions.TVRageException, e:
-            logger.log(u"Unable to add TVRage info: " + str(e), logger.WARNING)
+            logger.log(u"Unable to add TVRage info: " + ex(e), logger.WARNING)
 
 
 
@@ -390,9 +392,12 @@ class TVShow(object):
                 epObj = t[self.tvdbid].airedOn(parse_result.air_date)[0]
                 season = int(epObj["seasonnumber"])
                 episodes = [int(epObj["episodenumber"])]
-            except tvdb_exceptions.tvdb_episodenotfound, e:
+            except tvdb_exceptions.tvdb_episodenotfound:
                 logger.log(u"Unable to find episode with date "+str(episodes[0])+" for show "+self.name+", skipping", logger.WARNING)
                 return None
+            except tvdb_exceptions.tvdb_error, e:
+                logger.log(u"Unable to contact TVDB: "+ex(e), logger.WARNING)
+                return None
 
         for curEpNum in episodes:
 
@@ -538,6 +543,7 @@ class TVShow(object):
                 ltvdb_api_parms['language'] = self.lang
 
             t = tvdb_api.Tvdb(**ltvdb_api_parms)
+
         else:
             t = tvapi
 
@@ -595,14 +601,14 @@ class TVShow(object):
                 raise exceptions.NoNFOException("Empty <id> or <tvdbid> field in NFO")
 
         except (exceptions.NoNFOException, SyntaxError, ValueError), e:
-            logger.log(u"There was an error parsing your existing tvshow.nfo file: " + str(e), logger.ERROR)
+            logger.log(u"There was an error parsing your existing tvshow.nfo file: " + ex(e), logger.ERROR)
             logger.log(u"Attempting to rename it to tvshow.nfo.old", logger.DEBUG)
 
             try:
                 xmlFileObj.close()
                 ek.ek(os.rename, xmlFile, xmlFile + ".old")
             except Exception, e:
-                logger.log(u"Failed to rename your tvshow.nfo file - you need to delete it or fix it: " + str(e), logger.ERROR)
+                logger.log(u"Failed to rename your tvshow.nfo file - you need to delete it or fix it: " + ex(e), logger.ERROR)
             raise exceptions.NoNFOException("Invalid info in tvshow.nfo")
 
         if showXML.findtext('studio') != None:
@@ -753,7 +759,6 @@ class TVShow(object):
 
             goodName = rootEp.prettyName()
             actualName = os.path.splitext(os.path.basename(curLocation))
-            curEpDir = os.path.dirname(curLocation)
 
             if goodName == actualName[0]:
                 logger.log(str(self.tvdbid) + ": File " + rootEp.location + " is already named correctly, skipping", logger.DEBUG)
@@ -890,7 +895,7 @@ class TVShow(object):
             return Overview.GOOD
         elif epStatus in Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER:
 
-            anyQualities, bestQualities = Quality.splitQuality(self.quality)
+            anyQualities, bestQualities = Quality.splitQuality(self.quality) #@UnusedVariable
             if bestQualities:
                 maxBestQuality = max(bestQualities)
             else:
@@ -1078,7 +1083,7 @@ class TVEpisode(object):
                 myEp = cachedSeason[episode]
 
         except (tvdb_exceptions.tvdb_error, IOError), e:
-            logger.log(u"TVDB threw up an error: "+str(e).decode('utf-8'), logger.DEBUG)
+            logger.log(u"TVDB threw up an error: "+ex(e), logger.DEBUG)
             # if the episode is already valid just log it, if not throw it up
             if self.name:
                 logger.log(u"TVDB timed out but we have enough info from other sources, allowing the error", logger.DEBUG)
@@ -1198,11 +1203,11 @@ class TVEpisode(object):
                 try:
                     showXML = etree.ElementTree(file = nfoFile)
                 except (SyntaxError, ValueError), e:
-                    logger.log(u"Error loading the NFO, backing up the NFO and skipping for now: " + str(e), logger.ERROR) #TODO: figure out what's wrong and fix it
+                    logger.log(u"Error loading the NFO, backing up the NFO and skipping for now: " + ex(e), logger.ERROR) #TODO: figure out what's wrong and fix it
                     try:
                         ek.ek(os.rename, nfoFile, nfoFile + ".old")
                     except Exception, e:
-                        logger.log(u"Failed to rename your episode's NFO file - you need to delete it or fix it: " + str(e), logger.ERROR)
+                        logger.log(u"Failed to rename your episode's NFO file - you need to delete it or fix it: " + ex(e), logger.ERROR)
                     raise exceptions.NoNFOException("Error in NFO format")
 
                 for epDetails in showXML.getiterator('episodedetails'):
@@ -1420,7 +1425,7 @@ class TVEpisode(object):
             finalName += goodName
 
         if naming_quality:
-            epStatus, epQual = Quality.splitCompositeStatus(self.status)
+            epStatus, epQual = Quality.splitCompositeStatus(self.status) #@UnusedVariable
             if epQual != Quality.NONE:
                 finalName += config.naming_sep_type[naming_sep_type] + Quality.qualityStrings[epQual]
 
diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py
index f2b1fae485c8a678fc1caa0dedc05f24d5fbeac7..75a154838fc51db73b90ec536a0a54e30b7dae23 100644
--- a/sickbeard/tvcache.py
+++ b/sickbeard/tvcache.py
@@ -24,11 +24,11 @@ import sickbeard
 
 from sickbeard import db
 from sickbeard import logger
-from sickbeard.common import *
+from sickbeard.common import Quality
 
-from sickbeard import helpers, classes, exceptions, show_name_helpers
-from sickbeard import providers
+from sickbeard import helpers, exceptions, show_name_helpers
 from sickbeard import name_cache
+from sickbeard.exceptions import ex
 
 import xml.etree.cElementTree as etree
 
@@ -114,7 +114,7 @@ class TVCache():
             responseSoup = etree.ElementTree(etree.XML(data))
             items = responseSoup.getiterator('item')
         except Exception, e:
-            logger.log(u"Error trying to load "+self.provider.name+" RSS feed: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error trying to load "+self.provider.name+" RSS feed: "+ex(e), logger.ERROR)
             logger.log(u"Feed contents: "+repr(data), logger.DEBUG)
             return []
 
@@ -200,6 +200,8 @@ class TVCache():
             logger.log(u"No series name retrieved from "+name+", unable to cache it", logger.DEBUG)
             return False
 
+        tvdb_lang = None
+
         # if we need tvdb_id or tvrage_id then search the DB for them
         if not tvdb_id or not tvrage_id:
 
@@ -291,9 +293,12 @@ class TVCache():
                 epObj = t[tvdb_id].airedOn(parse_result.air_date)[0]
                 season = int(epObj["seasonnumber"])
                 episodes = [int(epObj["episodenumber"])]
-            except tvdb_exceptions.tvdb_episodenotfound, e:
+            except tvdb_exceptions.tvdb_episodenotfound:
                 logger.log(u"Unable to find episode with date "+str(parse_result.air_date)+" for show "+parse_result.series_name+", skipping", logger.WARNING)
                 return False
+            except tvdb_exceptions.tvdb_error, e:
+                logger.log(u"Unable to contact TVDB: "+ex(e), logger.WARNING)
+                return False
 
         episodeText = "|"+"|".join(map(str, episodes))+"|"
 
diff --git a/sickbeard/tvrage.py b/sickbeard/tvrage.py
index 91aaff411162effeb7646121982126ebd19e26a2..040f425e5e81389e1b6397e0a0e7f5b429581b42 100644
--- a/sickbeard/tvrage.py
+++ b/sickbeard/tvrage.py
@@ -25,10 +25,11 @@ import traceback
 import sickbeard
 
 from sickbeard import logger
-from sickbeard.common import *
+from sickbeard.common import UNAIRED
 
 from sickbeard import db
 from sickbeard import exceptions, helpers
+from sickbeard.exceptions import ex
 
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
@@ -137,7 +138,7 @@ class TVRage:
 
                 # if we couldn't compare with TVDB try comparing it with the local database
                 except tvdb_exceptions.tvdb_exception, e:
-                    logger.log(u"Unable to check TVRage info against TVDB: "+str(e).decode('utf-8'))
+                    logger.log(u"Unable to check TVRage info against TVDB: "+ex(e))
 
                     logger.log(u"Trying against DB instead", logger.DEBUG)
 
@@ -159,7 +160,7 @@ class TVRage:
                 logger.log(u"Date from TVRage for episode " + str(curSeason) + "x1: " + str(curEpInfo['airdate']), logger.DEBUG)
 
         except Exception, e:
-            logger.log(u"Error encountered while checking TVRage<->TVDB sync: " + str(e), logger.WARNING)
+            logger.log(u"Error encountered while checking TVRage<->TVDB sync: " + ex(e), logger.WARNING)
             logger.log(traceback.format_exc(), logger.DEBUG)
 
         return False
@@ -198,7 +199,7 @@ class TVRage:
                 airdate = datetime.date(rawAirdate[0], rawAirdate[1], rawAirdate[2])
 
             except tvdb_exceptions.tvdb_exception, e:
-                logger.log(u"Unable to check TVRage info against TVDB: "+str(e).decode('utf-8'))
+                logger.log(u"Unable to check TVRage info against TVDB: "+ex(e))
 
                 logger.log(u"Trying against DB instead", logger.DEBUG)
 
@@ -217,7 +218,7 @@ class TVRage:
                 return True
 
         except Exception, e:
-            logger.log(u"Error encountered while checking TVRage<->TVDB sync: " + str(e), logger.WARNING)
+            logger.log(u"Error encountered while checking TVRage<->TVDB sync: " + ex(e), logger.WARNING)
             logger.log(traceback.format_exc(), logger.DEBUG)
 
         return False
@@ -250,7 +251,7 @@ class TVRage:
         try:
             result = helpers.getURL(url).decode('utf-8')
         except (urllib2.HTTPError, IOError), e:
-            logger.log(u"Unable to load TVRage info: " + str(e))
+            logger.log(u"Unable to load TVRage info: " + ex(e))
             raise exceptions.TVRageException("urlopen call to " + url + " failed")
 
         urlData = result.splitlines()
@@ -296,10 +297,10 @@ class TVRage:
 
         try:
             date = datetime.datetime.strptime(epInfo[2], "%b/%d/%Y").date()
-        except ValueError, e:
+        except ValueError:
             try:
                 date = datetime.datetime.strptime(epInfo[2], "%d/%b/%Y").date()
-            except ValueError, e:
+            except ValueError:
                 logger.log(u"Unable to figure out the time from the TVRage data "+epInfo[2])
                 return None
 
@@ -335,6 +336,6 @@ class TVRage:
         try:
             ep = self.show.getEpisode(self.nextEpInfo['season'], self.nextEpInfo['episode'])
         except exceptions.SickBeardException, e:
-            logger.log(u"Unable to create episode from tvrage (could be for a variety of reasons): " + str(e))
+            logger.log(u"Unable to create episode from tvrage (could be for a variety of reasons): " + ex(e))
 
         return ep
diff --git a/sickbeard/ui.py b/sickbeard/ui.py
index 2cb39305aa570520234ee43c5e7a0ca96bd4e18f..256ce9809f0dc1a180e4dfcc3ec0d24529bfebfb 100644
--- a/sickbeard/ui.py
+++ b/sickbeard/ui.py
@@ -16,18 +16,99 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-
-
-import os.path
 import datetime
+import cherrypy
 import sickbeard
 
-from sickbeard import exceptions
-from sickbeard.tv import TVShow
-from sickbeard import logger
-from sickbeard import classes
+MESSAGE = 'notice'
+ERROR = 'error'
 
-from lib.tvdb_api import tvdb_exceptions
+class Notifications(object):
+    """
+    A queue of Notification objects.
+    """
+    def __init__(self):
+        self._messages = []
+        self._errors = []
+        
+    def message(self, title, message=''):
+        """
+        Add a regular notification to the queue
+        
+        title: The title of the notification
+        message: The message portion of the notification  
+        """
+        self._messages.append(Notification(title, message, MESSAGE))
+
+    def error(self, title, message=''):
+        """
+        Add an error notification to the queue
+
+        title: The title of the notification
+        message: The message portion of the notification  
+        """
+        self._errors.append(Notification(title, message, ERROR))
+
+    def get_notifications(self):
+        """
+        Return all the available notifications in a list. Marks them all as seen
+        as it returns them. Also removes timed out Notifications from the queue.
+        
+        Returns: A list of Notification objects
+        """
+        
+        # filter out expired notifications 
+        self._errors = [x for x in self._errors if not x.is_expired()]
+        self._messages = [x for x in self._messages if not x.is_expired()]
+        
+        # return any notifications that haven't been shown to the client already
+        return [x.see() for x in self._errors + self._messages if x.is_new()]
+
+# static notification queue object
+notifications = Notifications()
+
+    
+class Notification(object):
+    """
+    Represents a single notification. Tracks its own timeout and a list of which clients have
+    seen it before.
+    """
+    def __init__(self, title, message='', type=None, timeout=None):
+        self.title = title
+        self.message = message
+        
+        self._when = datetime.datetime.now()
+        self._seen = []
+
+        if type:
+            self.type = type
+        else:
+            self.type = MESSAGE
+        
+        if timeout:
+            self._timeout = timeout
+        else:
+            self._timeout = datetime.timedelta(minutes=1)
+
+    def is_new(self):
+        """
+        Returns True if the notification hasn't been displayed to the current client (aka IP address).
+        """
+        return cherrypy.request.remote.ip not in self._seen
+    
+    def is_expired(self):
+        """
+        Returns True if the notification is older than the specified timeout value.
+        """
+        return datetime.datetime.now() - self._when > self._timeout
+
+    
+    def see(self):
+        """
+        Returns this notification object and marks it as seen by the client ip
+        """
+        self._seen.append(cherrypy.request.remote.ip)
+        return self
 
 class ProgressIndicator():
 
@@ -76,7 +157,7 @@ class QueueProgressIndicator():
         return len([x for x in self.queueItemList if x.isInQueue()])
 
     def nextName(self):
-        for curItem in [sickbeard.showQueueScheduler.action.currentItem]+sickbeard.showQueueScheduler.action.queue:
+        for curItem in [sickbeard.showQueueScheduler.action.currentItem]+sickbeard.showQueueScheduler.action.queue: #@UndefinedVariable
             if curItem in self.queueItemList:
                 return curItem.name
 
@@ -96,57 +177,4 @@ class LoadingTVShow():
         self.dir = dir
         self.show = None
 
-def addShowsFromRootDir(dir):
-
-    returnStr = ""
-
-    if not os.path.isdir(dir):
-        return "Couldn't find directory " + dir
-
-    for curDir in os.listdir(unicode(dir)):
-        showDir = os.path.join(dir, curDir)
-        logStr = "Attempting to load show in " + showDir
-        logger.log(logStr, logger.DEBUG)
-        returnStr += logStr + "<br />\n"
-
-        sickbeard.loadingShowList[showDir] = LoadingTVShow(showDir)
-
-        try:
-            #myAdder = ShowAdder(showDir)
-            #myAdder.start()
-            sickbeard.showAddScheduler.action.addShowToQueue(showDir)
-        except exceptions.NoNFOException:
-            logStr = "Unable to automatically add the show in " + showDir
-            logger.log(logStr, logger.ERROR)
-            returnStr += logStr + "<br />\n"
-            del sickbeard.loadingShowList[showDir]
-        except exceptions.MultipleShowObjectsException:
-            logStr = "Show in "+showDir+" already exists, skipping..."
-            logger.log(logStr, logger.ERROR)
-            returnStr += logStr + "<br />\n"
-            del sickbeard.loadingShowList[showDir]
-
-    return returnStr
-
-class Flash:
-    _messages = []
-    _errors = []
-
-    def message(self, title, detail=''):
-        Flash._messages.append((title, detail))
-
-    def error(self, title, detail=''):
-        Flash._errors.append((title, detail))
-
-    def messages(self):
-        tempMessages = Flash._messages
-        Flash._messages = []
-        return tempMessages
-
-    def errors(self):
-        tempErrors = Flash._errors
-        Flash._errors = []
-        return tempErrors
-
-flash = Flash()
 
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index e91c7e0d0b9790764a0a0830e123ff2bb0cae65a..472d1b1b4981a9cd8ddde8a54c7a6089918d0ae2 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -17,11 +17,12 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import sickbeard
-from sickbeard import helpers, version, ui
+from sickbeard import version, ui
 from sickbeard import logger
 from sickbeard import scene_exceptions
+from sickbeard.exceptions import ex
 
-import os, os.path, platform, shutil, time
+import os, platform, shutil
 import subprocess, re
 import urllib, urllib2
 import zipfile, tarfile
@@ -89,7 +90,7 @@ class CheckVersion():
         if not self.updater.need_update():
             logger.log(u"No update needed")
             if force:
-                ui.flash.message('No update needed')
+                ui.notifications.message('No update needed')
             return False
 
         self.updater.set_newest_text()
@@ -160,7 +161,7 @@ class WindowsUpdateManager(UpdateManager):
         # download the zip
         try:
             logger.log(u"Downloading update file from "+str(new_link))
-            (filename, headers) = urllib.urlretrieve(new_link)
+            (filename, headers) = urllib.urlretrieve(new_link) #@UnusedVariable
 
             # prepare the update dir
             sb_update_dir = os.path.join(sickbeard.PROG_DIR, 'sb-update')
@@ -177,7 +178,7 @@ class WindowsUpdateManager(UpdateManager):
             # find update dir name
             update_dir_contents = os.listdir(sb_update_dir)
             if len(update_dir_contents) != 1:
-                logger.log("Invalid update data, update failed.", logger.ERROR)
+                logger.log("Invalid update data, update failed. Maybe try deleting your sb-update folder?", logger.ERROR)
                 return False
 
             content_dir = os.path.join(sb_update_dir, update_dir_contents[0])
@@ -191,7 +192,7 @@ class WindowsUpdateManager(UpdateManager):
             os.remove(filename)
 
         except Exception, e:
-            logger.log(u"Error while trying to update: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Error while trying to update: "+ex(e), logger.ERROR)
             return False
 
         return True
@@ -233,7 +234,7 @@ class GitUpdateManager(UpdateManager):
                 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=sickbeard.PROG_DIR)
                 output, err = p.communicate()
                 logger.log(u"git output: "+output, logger.DEBUG)
-            except OSError, e:
+            except OSError:
                 logger.log(u"Command "+cmd+" didn't work, couldn't find git.")
                 continue
             
@@ -258,7 +259,7 @@ class GitUpdateManager(UpdateManager):
         Returns: True for success or False for failure
         """
 
-        output, err = self._run_git('rev-parse HEAD')
+        output, err = self._run_git('rev-parse HEAD') #@UnusedVariable
 
         if not output:
             return self._git_error()
@@ -328,7 +329,7 @@ class GitUpdateManager(UpdateManager):
         self._find_installed_version()
         try:
             self._check_github_for_update()
-        except Exception, e:
+        except Exception:
             logger.log(u"Unable to contact github, can't check for update", logger.ERROR)
             return False
 
@@ -345,7 +346,7 @@ class GitUpdateManager(UpdateManager):
         on the call's success.
         """
 
-        output, err = self._run_git('pull origin '+sickbeard.version.SICKBEARD_VERSION)
+        output, err = self._run_git('pull origin '+sickbeard.version.SICKBEARD_VERSION) #@UnusedVariable
 
         if not output:
             return self._git_error()
@@ -462,7 +463,7 @@ class SourceUpdateManager(GitUpdateManager):
         content_dir = os.path.join(sb_update_dir, update_dir_contents[0])
 
         # walk temp folder and move files to main folder
-        for dirname, dirnames, filenames in os.walk(content_dir):
+        for dirname, dirnames, filenames in os.walk(content_dir): #@UnusedVariable
             dirname = dirname[len(content_dir)+1:]
             for curfile in filenames:
                 old_path = os.path.join(content_dir, dirname, curfile)
@@ -478,7 +479,7 @@ class SourceUpdateManager(GitUpdateManager):
             ver_file.write(self._newest_commit_hash)
             ver_file.close()
         except IOError, e:
-            logger.log(u"Unable to write version file, update not complete: "+str(e).decode('utf-8'), logger.ERROR)
+            logger.log(u"Unable to write version file, update not complete: "+ex(e), logger.ERROR)
             return False
 
         return True
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index 9d06b381d1493da5684bc2e391e780015b072d85..4b2ec79aa405fb6a3e34f6f64eab522385441e94 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -25,28 +25,25 @@ import urllib
 import re
 import threading
 import datetime
-import operator
 
 from Cheetah.Template import Template
-import cherrypy
 import cherrypy.lib
 
-import metadata.helpers
+import sickbeard
 
 from sickbeard import config
-from sickbeard import history, notifiers, processTV, search, providers
-from sickbeard import tv, versionChecker, ui
+from sickbeard import history, notifiers, processTV
+from sickbeard import tv, ui
 from sickbeard import logger, helpers, exceptions, classes, db
 from sickbeard import encodingKludge as ek
 from sickbeard import search_queue
 from sickbeard import image_cache
 
-from sickbeard.notifiers import xbmc
-from sickbeard.notifiers import plex
 from sickbeard.providers import newznab
-from sickbeard.common import *
+from sickbeard.common import Quality, Overview, statusStrings
+from sickbeard.common import SNATCHED, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED
+from sickbeard.exceptions import ex
 
-from lib.tvdb_api import tvdb_exceptions
 from lib.tvdb_api import tvdb_api
 
 try:
@@ -56,8 +53,6 @@ except ImportError:
 
 import xml.etree.cElementTree as etree
 
-import sickbeard
-
 from sickbeard import browser
 
 
@@ -81,7 +76,6 @@ class PageTemplate (Template):
             { 'title': 'Config',          'key': 'config'         },
             { 'title': logPageTitle,      'key': 'errorlogs'      },
         ]
-        self.flash = ui.Flash()
 
 def redirect(abspath, *args, **KWs):
     assert abspath[0] == '/'
@@ -139,9 +133,9 @@ class ManageSearches:
     def index(self):
         t = PageTemplate(file="manage_manageSearches.tmpl")
         #t.backlogPI = sickbeard.backlogSearchScheduler.action.getProgressIndicator()
-        t.backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused()
-        t.backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress()
-        t.searchStatus = sickbeard.currentSearchScheduler.action.amActive
+        t.backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused() #@UndefinedVariable
+        t.backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress() #@UndefinedVariable
+        t.searchStatus = sickbeard.currentSearchScheduler.action.amActive #@UndefinedVariable
         t.submenu = ManageMenu
 
         return _munge(t)
@@ -153,7 +147,7 @@ class ManageSearches:
         result = sickbeard.currentSearchScheduler.forceRun()
         if result:
             logger.log(u"Search forced")
-            ui.flash.message('Episode search started',
+            ui.notifications.message('Episode search started',
                           'Note: RSS feeds may not be updated if retrieved recently')
 
         redirect("/manage/manageSearches")
@@ -161,9 +155,9 @@ class ManageSearches:
     @cherrypy.expose
     def pauseBacklog(self, paused=None):
         if paused == "1":
-            sickbeard.searchQueueScheduler.action.pause_backlog()
+            sickbeard.searchQueueScheduler.action.pause_backlog() #@UndefinedVariable
         else:
-            sickbeard.searchQueueScheduler.action.unpause_backlog()
+            sickbeard.searchQueueScheduler.action.unpause_backlog() #@UndefinedVariable
 
         redirect("/manage/manageSearches")
 
@@ -171,7 +165,7 @@ class ManageSearches:
     def forceVersionCheck(self):
 
         # force a check to see if there is a new version
-        result = sickbeard.versionCheckScheduler.action.check_for_new_version(force=True)
+        result = sickbeard.versionCheckScheduler.action.check_for_new_version(force=True) #@UndefinedVariable
         if result:
             logger.log(u"Forcing version check")
 
@@ -284,7 +278,7 @@ class Manage:
                 all_eps = [str(x["season"])+'x'+str(x["episode"]) for x in all_eps_results]
                 to_change[cur_tvdb_id] = all_eps
 
-            result = Home().setStatus(cur_tvdb_id, '|'.join(to_change[cur_tvdb_id]), newStatus, direct=True)
+            Home().setStatus(cur_tvdb_id, '|'.join(to_change[cur_tvdb_id]), newStatus, direct=True)
             
         redirect('/manage/episodeStatuses')
 
@@ -294,7 +288,7 @@ class Manage:
         show_obj = helpers.findCertainShow(sickbeard.showList, int(tvdb_id))
         
         if show_obj:
-            sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj])
+            sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj]) #@UndefinedVariable
         
         redirect("/manage/backlogOverview")
         
@@ -450,7 +444,7 @@ class Manage:
                 errors.append('<b>%s:</b><br />\n<ul>' % showObj.name + '\n'.join(['<li>%s</li>' % error for error in curErrors]) + "</ul>")
 
         if len(errors) > 0:
-            ui.flash.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"),
+            ui.notifications.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"),
                         "<br />\n".join(errors))
 
         redirect("/manage")
@@ -505,25 +499,25 @@ class Manage:
 
             if curShowID in toUpdate:
                 try:
-                    sickbeard.showQueueScheduler.action.updateShow(showObj, True)
+                    sickbeard.showQueueScheduler.action.updateShow(showObj, True) #@UndefinedVariable
                     updates.append(showObj.name)
                 except exceptions.CantUpdateException, e:
-                    errors.append("Unable to update show "+showObj.name+": "+str(e).decode('utf-8'))
+                    errors.append("Unable to update show "+showObj.name+": "+ex(e))
 
             # don't bother refreshing shows that were updated anyway
             if curShowID in toRefresh and curShowID not in toUpdate:
                 try:
-                    sickbeard.showQueueScheduler.action.refreshShow(showObj)
+                    sickbeard.showQueueScheduler.action.refreshShow(showObj) #@UndefinedVariable
                     refreshes.append(showObj.name)
                 except exceptions.CantRefreshException, e:
-                    errors.append("Unable to refresh show "+showObj.name+": "+str(e).decode('utf-8'))
+                    errors.append("Unable to refresh show "+showObj.name+": "+ex(e))
 
             if curShowID in toRename:
-                sickbeard.showQueueScheduler.action.renameShowEpisodes(showObj)
+                sickbeard.showQueueScheduler.action.renameShowEpisodes(showObj) #@UndefinedVariable
                 renames.append(showObj.name)
 
         if len(errors) > 0:
-            ui.flash.error("Errors encountered",
+            ui.notifications.error("Errors encountered",
                         '<br >\n'.join(errors))
 
         messageDetail = ""
@@ -544,7 +538,7 @@ class Manage:
             messageDetail += "</li></ul>"
 
         if len(updates+refreshes+renames) > 0:
-            ui.flash.message("The following actions were queued:",
+            ui.notifications.message("The following actions were queued:",
                           messageDetail)
 
         redirect("/manage")
@@ -575,7 +569,7 @@ class History:
 
         myDB = db.DBConnection()
         myDB.action("DELETE FROM history WHERE 1=1")
-        ui.flash.message('History cleared')
+        ui.notifications.message('History cleared')
         redirect("/history")
 
 
@@ -584,7 +578,7 @@ class History:
 
         myDB = db.DBConnection()
         myDB.action("DELETE FROM history WHERE date < "+str((datetime.datetime.today()-datetime.timedelta(days=30)).strftime(history.dateFormat)))
-        ui.flash.message('Removed history entries greater than 30 days old')
+        ui.notifications.message('Removed history entries greater than 30 days old')
         redirect("/history")
 
 
@@ -680,10 +674,10 @@ class ConfigGeneral:
         if len(results) > 0:
             for x in results:
                 logger.log(x, logger.ERROR)
-            ui.flash.error('Error(s) Saving Configuration',
+            ui.notifications.error('Error(s) Saving Configuration',
                         '<br />\n'.join(results))
         else:
-            ui.flash.message('Configuration Saved', ek.ek(os.path.join, sickbeard.PROG_DIR, 'config.ini') )
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE) )
 
         redirect("/config/general/")
 
@@ -761,10 +755,10 @@ class ConfigSearch:
         if len(results) > 0:
             for x in results:
                 logger.log(x, logger.ERROR)
-            ui.flash.error('Error(s) Saving Configuration',
+            ui.notifications.error('Error(s) Saving Configuration',
                         '<br />\n'.join(results))
         else:
-            ui.flash.message('Configuration Saved', ek.ek(os.path.join, sickbeard.PROG_DIR, 'config.ini') )
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE) )
 
         redirect("/config/search/")
 
@@ -868,10 +862,10 @@ class ConfigPostProcessing:
         if len(results) > 0:
             for x in results:
                 logger.log(x, logger.ERROR)
-            ui.flash.error('Error(s) Saving Configuration',
+            ui.notifications.error('Error(s) Saving Configuration',
                         '<br />\n'.join(results))
         else:
-            ui.flash.message('Configuration Saved', ek.ek(os.path.join, sickbeard.PROG_DIR, 'config.ini') )
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE) )
 
         redirect("/config/postProcessing/")
 
@@ -1024,12 +1018,12 @@ class ConfigProviders:
 
 
     @cherrypy.expose
-    def saveProviders(self, tvbinz_uid=None, tvbinz_hash=None, nzbs_org_uid=None,
-                      nzbs_org_hash=None, nzbmatrix_username=None, nzbmatrix_apikey=None,
-                      tvbinz_auth=None, provider_order=None,
+    def saveProviders(self, nzbs_org_uid=None, nzbs_org_hash=None,
+                      nzbmatrix_username=None, nzbmatrix_apikey=None,
                       nzbs_r_us_uid=None, nzbs_r_us_hash=None, newznab_string=None,
                       tvtorrents_digest=None, tvtorrents_hash=None, 
-                      newzbin_username=None, newzbin_password=None):
+                      newzbin_username=None, newzbin_password=None,
+                      provider_order=None):
 
         results = []
 
@@ -1075,10 +1069,7 @@ class ConfigProviders:
 
             provider_list.append(curProvider)
 
-            if curProvider == 'tvbinz':
-                if curEnabled or sickbeard.SHOW_TVBINZ:
-                    sickbeard.TVBINZ = curEnabled
-            elif curProvider == 'nzbs_org':
+            if curProvider == 'nzbs_org':
                 sickbeard.NZBS = curEnabled
             elif curProvider == 'nzbs_r_us':
                 sickbeard.NZBSRUS = curEnabled
@@ -1099,13 +1090,6 @@ class ConfigProviders:
             else:
                 logger.log(u"don't know what "+curProvider+" is, skipping")
 
-        if tvbinz_uid:
-            sickbeard.TVBINZ_UID = tvbinz_uid.strip()
-        if tvbinz_hash:
-            sickbeard.TVBINZ_HASH = tvbinz_hash.strip()
-        if tvbinz_auth:
-            sickbeard.TVBINZ_AUTH = tvbinz_auth.strip()
-            
         sickbeard.TVTORRENTS_DIGEST = tvtorrents_digest.strip()
         sickbeard.TVTORRENTS_HASH = tvtorrents_hash.strip()
 
@@ -1128,10 +1112,10 @@ class ConfigProviders:
         if len(results) > 0:
             for x in results:
                 logger.log(x, logger.ERROR)
-            ui.flash.error('Error(s) Saving Configuration',
+            ui.notifications.error('Error(s) Saving Configuration',
                         '<br />\n'.join(results))
         else:
-            ui.flash.message('Configuration Saved', ek.ek(os.path.join, sickbeard.PROG_DIR, 'config.ini') )
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE) )
 
         redirect("/config/providers/")
 
@@ -1318,10 +1302,10 @@ class ConfigNotifications:
         if len(results) > 0:
             for x in results:
                 logger.log(x, logger.ERROR)
-            ui.flash.error('Error(s) Saving Configuration',
+            ui.notifications.error('Error(s) Saving Configuration',
                         '<br />\n'.join(results))
         else:
-            ui.flash.message('Configuration Saved', ek.ek(os.path.join, sickbeard.PROG_DIR, 'config.ini') )
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE) )
 
         redirect("/config/notifications/")
 
@@ -1426,7 +1410,7 @@ class NewHomeAddShows:
         try:
             seriesXML = etree.ElementTree(etree.XML(urlData))
         except Exception, e:
-            logger.log(u"Unable to parse XML for some reason: "+str(e).decode('utf-8')+" from XML: "+urlData, logger.ERROR)
+            logger.log(u"Unable to parse XML for some reason: "+ex(e)+" from XML: "+urlData, logger.ERROR)
             return ''
 
         series = seriesXML.getiterator('Series')
@@ -1601,14 +1585,14 @@ class NewHomeAddShows:
         
         # blanket policy - if the dir exists you should have used "add existing show" numbnuts
         if ek.ek(os.path.isdir, show_dir) and not fullShowPath:
-            ui.flash.error("Unable to add show", "Folder "+show_dir+" exists already")
+            ui.notifications.error("Unable to add show", "Folder "+show_dir+" exists already")
             redirect('/home')
         
         # create the dir and make sure it worked
         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)
-            ui.flash.error("Unable to add show", "Unable to create the folder "+show_dir+", can't add the show")
+            ui.notifications.error("Unable to add show", "Unable to create the folder "+show_dir+", can't add the show")
             redirect("/home")
         else:
             helpers.chmodAsParent(show_dir)
@@ -1630,8 +1614,8 @@ class NewHomeAddShows:
         newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
         
         # add the show
-        sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, int(defaultStatus), newQuality, seasonFolders, tvdbLang)
-        ui.flash.message('Show added', 'Adding the specified show into '+show_dir)
+        sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, int(defaultStatus), newQuality, seasonFolders, tvdbLang) #@UndefinedVariable
+        ui.notifications.message('Show added', 'Adding the specified show into '+show_dir)
 
         return finishAddShow()
         
@@ -1701,11 +1685,11 @@ class NewHomeAddShows:
             show_dir, tvdb_id, show_name = cur_show
 
             # add the show
-            sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, SKIPPED, sickbeard.QUALITY_DEFAULT, sickbeard.SEASON_FOLDERS_DEFAULT)
+            sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, SKIPPED, sickbeard.QUALITY_DEFAULT, sickbeard.SEASON_FOLDERS_DEFAULT) #@UndefinedVariable
             num_added += 1
          
         if num_added:
-            ui.flash.message("Shows Added", "Automatically added "+str(num_added)+" from their existing metadata files")
+            ui.notifications.message("Shows Added", "Automatically added "+str(num_added)+" from their existing metadata files")
 
         # if we're done then go home
         if not dirs_only:
@@ -1748,8 +1732,8 @@ class ErrorLogs:
         minLevel = int(minLevel)
 
         data = []
-        if os.path.isfile(logger.logFile):
-            f = ek.ek(open, logger.logFile)
+        if os.path.isfile(logger.sb_log_instance.log_file):
+            f = ek.ek(open, logger.sb_log_instance.log_file)
             data = f.readlines()
             f.close()
 
@@ -1828,9 +1812,9 @@ class Home:
             pw_append = " with password: " + password
 
         if result:
-            return "Test growl sent successfully to "+urllib.unquote_plus(host)+pw_append
+            return "Registered and Tested growl successfully "+urllib.unquote_plus(host)+pw_append
         else:
-            return "Test growl failed to "+urllib.unquote_plus(host)+pw_append
+            return "Registration and Testing of growl failed "+urllib.unquote_plus(host)+pw_append
 
     @cherrypy.expose
     def testProwl(self, prowl_api=None, prowl_priority=0):
@@ -1959,7 +1943,7 @@ class Home:
         if str(pid) != str(sickbeard.PID):
             redirect("/home")
 
-        updated = sickbeard.versionCheckScheduler.action.update()
+        updated = sickbeard.versionCheckScheduler.action.update() #@UndefinedVariable
 
         if updated:
             # do a hard restart
@@ -2001,23 +1985,25 @@ class Home:
         except sickbeard.exceptions.ShowDirNotFoundException:
             t.showLoc = (showObj._location, False)
 
-        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj):
-            ui.flash.message('This show is in the process of being downloaded from theTVDB.com - the info below is incomplete.')
+        show_message = ''
 
-        elif sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):
-            ui.flash.message('The information below is in the process of being updated.')
+        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj): #@UndefinedVariable
+            show_message = 'This show is in the process of being downloaded from theTVDB.com - the info below is incomplete.'
 
-        elif sickbeard.showQueueScheduler.action.isBeingRefreshed(showObj):
-            ui.flash.message('The episodes below are currently being refreshed from disk')
+        elif sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): #@UndefinedVariable
+            show_message = 'The information below is in the process of being updated.'
 
-        elif sickbeard.showQueueScheduler.action.isInRefreshQueue(showObj):
-            ui.flash.message('This show is queued to be refreshed.')
+        elif sickbeard.showQueueScheduler.action.isBeingRefreshed(showObj): #@UndefinedVariable
+            show_message = 'The episodes below are currently being refreshed from disk'
 
-        elif sickbeard.showQueueScheduler.action.isInUpdateQueue(showObj):
-            ui.flash.message('This show is queued and awaiting an update.')
+        elif sickbeard.showQueueScheduler.action.isInRefreshQueue(showObj): #@UndefinedVariable
+            show_message = 'This show is queued to be refreshed.'
 
-        if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj):
-            if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):
+        elif sickbeard.showQueueScheduler.action.isInUpdateQueue(showObj): #@UndefinedVariable
+            show_message = 'This show is queued and awaiting an update.'
+
+        if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj): #@UndefinedVariable
+            if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): #@UndefinedVariable
                 t.submenu.append({ 'title': 'Delete',            'path': 'home/deleteShow?show=%d'%showObj.tvdbid, 'confirm': True })
                 t.submenu.append({ 'title': 'Re-scan files',           'path': 'home/refreshShow?show=%d'%showObj.tvdbid })
                 t.submenu.append({ 'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&amp;force=1'%showObj.tvdbid })
@@ -2027,6 +2013,7 @@ class Home:
         t.show = showObj
         t.sqlResults = sqlResults
         t.seasonResults = seasonResults
+        t.show_message = show_message
 
         epCounts = {}
         epCats = {}
@@ -2130,9 +2117,9 @@ class Home:
             if bool(showObj.seasonfolders) != bool(seasonfolders):
                 showObj.seasonfolders = seasonfolders
                 try:
-                    sickbeard.showQueueScheduler.action.refreshShow(showObj)
+                    sickbeard.showQueueScheduler.action.refreshShow(showObj) #@UndefinedVariable
                 except exceptions.CantRefreshException, e:
-                    errors.append("Unable to refresh this show: "+str(e).decode('utf-8'))
+                    errors.append("Unable to refresh this show: "+ex(e))
 
             showObj.paused = paused
             showObj.air_by_date = air_by_date
@@ -2150,9 +2137,9 @@ class Home:
                     try:
                         showObj.location = location
                         try:
-                            sickbeard.showQueueScheduler.action.refreshShow(showObj)
+                            sickbeard.showQueueScheduler.action.refreshShow(showObj) #@UndefinedVariable
                         except exceptions.CantRefreshException, e:
-                            errors.append("Unable to refresh this show:"+str(e).decode('utf-8'))
+                            errors.append("Unable to refresh this show:"+ex(e))
                         # grab updated info from TVDB
                         #showObj.loadEpisodesFromTVDB()
                         # rescan the episodes in the new folder
@@ -2165,7 +2152,7 @@ class Home:
         # force the update
         if do_update:
             try:
-                sickbeard.showQueueScheduler.action.updateShow(showObj, True)
+                sickbeard.showQueueScheduler.action.updateShow(showObj, True) #@UndefinedVariable
                 time.sleep(1)
             except exceptions.CantUpdateException, e:
                 errors.append("Unable to force an update on the show.")
@@ -2174,7 +2161,7 @@ class Home:
             return errors
 
         if len(errors) > 0:
-            ui.flash.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"),
+            ui.notifications.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"),
                         '<ul>' + '\n'.join(['<li>%s</li>' % error for error in errors]) + "</ul>")
 
         redirect("/home/displayShow?show=" + show)
@@ -2190,13 +2177,12 @@ class Home:
         if showObj == None:
             return _genericMessage("Error", "Unable to find the specified show")
 
-        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj) or \
-        sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):
+        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): #@UndefinedVariable
             return _genericMessage("Error", "Shows can't be deleted while they're being added or updated.")
 
         showObj.deleteShow()
 
-        ui.flash.message('<b>%s</b> has been deleted' % showObj.name)
+        ui.notifications.message('<b>%s</b> has been deleted' % showObj.name)
         redirect("/home")
 
     @cherrypy.expose
@@ -2212,10 +2198,10 @@ class Home:
 
         # force the update from the DB
         try:
-            sickbeard.showQueueScheduler.action.refreshShow(showObj)
+            sickbeard.showQueueScheduler.action.refreshShow(showObj) #@UndefinedVariable
         except exceptions.CantRefreshException, e:
-            ui.flash.error("Unable to refresh this show.",
-                        str(e))
+            ui.notifications.error("Unable to refresh this show.",
+                        ex(e))
 
         time.sleep(3)
 
@@ -2234,10 +2220,10 @@ class Home:
 
         # force the update
         try:
-            sickbeard.showQueueScheduler.action.updateShow(showObj, bool(force))
+            sickbeard.showQueueScheduler.action.updateShow(showObj, bool(force)) #@UndefinedVariable
         except exceptions.CantUpdateException, e:
-            ui.flash.error("Unable to update this show.",
-                        str(e))
+            ui.notifications.error("Unable to update this show.",
+                        ex(e))
 
         # just give it some time
         time.sleep(3)
@@ -2250,9 +2236,9 @@ class Home:
 
         for curHost in [x.strip() for x in sickbeard.XBMC_HOST.split(",")]:
             if notifiers.xbmc_notifier._update_library(curHost, showName=showName):
-                ui.flash.message("Command sent to XBMC host " + curHost + " to update library")
+                ui.notifications.message("Command sent to XBMC host " + curHost + " to update library")
             else:
-                ui.flash.error("Unable to contact XBMC host " + curHost)
+                ui.notifications.error("Unable to contact XBMC host " + curHost)
         redirect('/home')
 
 
@@ -2260,10 +2246,10 @@ class Home:
     def updatePLEX(self):
 
         if notifiers.plex_notifier._update_library():
-            ui.flash.message("Command sent to Plex Media Server host " + sickbeard.PLEX_HOST + " to update library")
+            ui.notifications.message("Command sent to Plex Media Server host " + sickbeard.PLEX_HOST + " to update library")
             logger.log(u"Plex library update initiated for host " + sickbeard.PLEX_HOST, logger.DEBUG)
         else:
-            ui.flash.error("Unable to contact Plex Media Server host " + sickbeard.PLEX_HOST)
+            ui.notifications.error("Unable to contact Plex Media Server host " + sickbeard.PLEX_HOST)
             logger.log(u"Plex library update failed for host " + sickbeard.PLEX_HOST, logger.ERROR)
         redirect('/home')
 
@@ -2279,7 +2265,7 @@ class Home:
         if showObj == None:
             return _genericMessage("Error", "Unable to find the specified show")
 
-        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj):
+        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj): #@UndefinedVariable
             return _genericMessage("Error", "Show is still being added, wait until it is finished before you rename files")
 
         showObj.fixEpisodeNames()
@@ -2292,7 +2278,7 @@ class Home:
         if show == None or eps == None or status == None:
             errMsg = "You must specify a show and at least one episode"
             if direct:
-                ui.flash.error('Error', errMsg)
+                ui.notifications.error('Error', errMsg)
                 return json.dumps({'result': 'error'})
             else:
                 return _genericMessage("Error", errMsg)
@@ -2300,7 +2286,7 @@ class Home:
         if not statusStrings.has_key(int(status)):
             errMsg = "Invalid status"
             if direct:
-                ui.flash.error('Error', errMsg)
+                ui.notifications.error('Error', errMsg)
                 return json.dumps({'result': 'error'})
             else:
                 return _genericMessage("Error", errMsg)
@@ -2310,7 +2296,7 @@ class Home:
         if showObj == None:
             errMsg = "Error", "Show not in show list"
             if direct:
-                ui.flash.error('Error', errMsg)
+                ui.notifications.error('Error', errMsg)
                 return json.dumps({'result': 'error'})
             else:
                 return _genericMessage("Error", errMsg)
@@ -2358,11 +2344,11 @@ class Home:
             msg += "<li>Season "+str(cur_segment)+"</li>"
             logger.log(u"Sending backlog for "+showObj.name+" season "+str(cur_segment)+" because some eps were set to wanted")
             cur_backlog_queue_item = search_queue.BacklogQueueItem(showObj, cur_segment)
-            sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item)
+            sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item) #@UndefinedVariable
         msg += "</ul>"
 
         if segment_list:
-            ui.flash.message("Backlog started", msg)
+            ui.notifications.message("Backlog started", msg)
 
         if direct:
             return json.dumps({'result': 'success'})
@@ -2372,40 +2358,46 @@ class Home:
     @cherrypy.expose
     def searchEpisode(self, show=None, season=None, episode=None):
 
-        outStr = ""
-        epObj = _getEpisode(show, season, episode)
-
-        if isinstance(epObj, str):
-            return _genericMessage("Error", epObj)
+        # retrieve the episode object and fail if we can't get one 
+        ep_obj = _getEpisode(show, season, episode)
+        if isinstance(ep_obj, str):
+            return json.dumps({'result': 'failure'})
 
-        tempStr = "Searching for download for " + epObj.prettyName(True)
-        logger.log(tempStr)
-        outStr += tempStr + "<br />\n"
-        foundEpisode = search.findEpisode(epObj, manualSearch=True)
+        # make a queue item for it and put it on the queue
+        ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj)
+        sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) #@UndefinedVariable
 
-        if not foundEpisode:
-            message = 'No downloads were found'
-            ui.flash.error(message, "Couldn't find a download for <i>%s</i>" % epObj.prettyName(True))
-            logger.log(message)
+        # wait until the queue item tells us whether it worked or not
+        while ep_queue_item.success == None: #@UndefinedVariable
+            time.sleep(1)
 
-        else:
+        # return the correct json value
+        if ep_queue_item.success:
+            return json.dumps({'result': statusStrings[ep_obj.status]})
 
-            # just use the first result for now
-            logger.log(u"Downloading episode from " + foundEpisode.url)
-            result = search.snatchEpisode(foundEpisode)
-            providerModule = foundEpisode.provider
-            if not result:
-                ui.flash.error('Error while attempting to snatch '+foundEpisode.name+', check your logs')
-            elif providerModule == None:
-                ui.flash.error('Provider is configured incorrectly, unable to download')
-            else:
-                ui.flash.message('Episode <b>%s</b> snatched from <b>%s</b>' % (foundEpisode.name, providerModule.name))
+        return json.dumps({'result': 'failure'})
 
-            #TODO: check if the download was successful
-
-        redirect("/home/displayShow?show=" + str(epObj.show.tvdbid))
+class UI:
+    
+    @cherrypy.expose
+    def add_message(self):
+        
+        ui.notifications.message('Test 1', 'This is test number 1')
+        ui.notifications.error('Test 2', 'This is test number 2')
+        
+        return "ok"
 
+    @cherrypy.expose
+    def get_messages(self):
+        messages = {}
+        cur_notification_num = 1
+        for cur_notification in ui.notifications.get_notifications():
+            messages['notification-'+str(cur_notification_num)] = {'title': cur_notification.title,
+                                                                   'message': cur_notification.message,
+                                                                   'type': cur_notification.type}
+            cur_notification_num += 1
 
+        return json.dumps(messages)
 
 class WebInterface:
 
@@ -2560,3 +2552,5 @@ class WebInterface:
     browser = browser.WebFileBrowser()
 
     errorlogs = ErrorLogs()
+    
+    ui = UI()
diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py
index 26275c159f733ee52bcafdd98633122454ef3ffc..42fac12fff71f1576ae64f19bb343bd6a5802903 100644
--- a/sickbeard/webserveInit.py
+++ b/sickbeard/webserveInit.py
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-import cherrypy
+#import cherrypy
 import cherrypy.lib.auth_basic
 import os.path
 
diff --git a/tests/scene_helpers_tests.py b/tests/scene_helpers_tests.py
index 6f4fc626b2c5a5277de4738ee735c3e8716b4c80..36206d646bd68f05d52b9ab7c78216c3b0af2468 100644
--- a/tests/scene_helpers_tests.py
+++ b/tests/scene_helpers_tests.py
@@ -3,7 +3,7 @@ import unittest
 import sys, os.path
 sys.path.append(os.path.abspath('..'))
 
-from sickbeard import show_name_helpers, scene_exceptions, common
+from sickbeard import show_name_helpers, scene_exceptions, common, name_cache
 
 import sickbeard
 from sickbeard import db
@@ -79,6 +79,7 @@ class SceneTests(unittest.TestCase):
     def test_filterBadReleases(self):
         
         self._test_filterBadReleases('Show.S02.German.Stuff-Grp', False)
+        self._test_filterBadReleases('Show.S02.Some.Stuff-Core2HD', False)
         self._test_filterBadReleases('Show.S02.Some.German.Stuff-Grp', False)
         self._test_filterBadReleases('German.Show.S02.Some.Stuff-Grp', True)
         self._test_filterBadReleases('Show.S02.This.Is.German', False)
@@ -107,6 +108,24 @@ class SceneExceptionTestCase(unittest.TestCase):
     def test_sceneExceptionByNameEmpty(self):
         self.assertEqual(scene_exceptions.get_scene_exception_by_name('nothing useful'), None)
 
+    def test_sceneExceptionsResetNameCache(self):
+        # clear the exceptions
+        myDB = db.DBConnection("cache.db")
+        myDB.action("DELETE FROM scene_exceptions")
+        
+        # put something in the cache
+        name_cache.addNameToCache('Cached Name', 0)
+        
+        # updating should clear the cache so our previously "Cached Name" won't be in there
+        scene_exceptions.retrieve_exceptions()
+        self.assertEqual(name_cache.retrieveNameFromCache('Cached Name'), None)
+
+        # put something in the cache
+        name_cache.addNameToCache('Cached Name', 0)
+        
+        # updating should not clear the cache this time since our exceptions didn't change
+        scene_exceptions.retrieve_exceptions()
+        self.assertEqual(name_cache.retrieveNameFromCache('Cached Name'), 0)
 
 
 if __name__ == '__main__':