diff --git a/.gitignore b/.gitignore
index 33ad38e98b6708fb6f559c091577384c7068e5cb..0108acaf4f5cb13667bfbbc14b83d92a8433343c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-#  SB User Related   #
+#  SR User Related   #
 ######################
 cache/*
 cache.db*
@@ -11,11 +11,13 @@ server.crt
 server.key
 restore/
 
-#  SB Test Related   #
+#  SR Test Related   #
 ######################
 tests/Logs/*
-tests/sickbeard.*
-tests/cache.db
+tests/cache/*
+tests/sickbeard.db*
+tests/cache.db*
+tests/failed.db
 
 #  Compiled source   #
 ######################
@@ -46,4 +48,4 @@ Thumbs.db
 .directory
 *~
 /.idea/
-*.torrent
+*.torrent
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 2911cc1207b53bf12983178401b8ce22d3f92aa4..e87c996c72adfe54bd0ab4ab0aee6f558b63ad0d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,4 +17,4 @@ script:
   - ./tests/all_tests.py
 
 notifications:
-  irc: "irc.freenode.net#sickrage"
+  irc: "irc.freenode.net#sickrage"
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
index f7206bb17155bc24dd316eeaad18b5f045758952..ae53e9f0548897f22524918f031d5f8afcbc0d73 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -49,7 +49,7 @@
 * Add image to be used when Trakt posters are void on Add Show/Add Trending Show page
 * Fix growl registration not sending sickrage an update notification registration
 * Add an anonymous redirect builder for external links
-* Update xbmc link to Kodi at Config Notifications
+* Update kodi link to Kodi at Config Notifications
 * Fix missing url for kickasstorrents in config_providers
 * Fix post processing when using tvrage indexer and mediabrowser metadata generation
 * Change reporting failed network_timezones.txt updates from an error to a warning
diff --git a/SickBeard.py b/SickBeard.py
index 0ffdc4228a3a9cf7ee6b9c9d997965c6fd3e57e9..8ff445f46fef6e7972684356b15bf60ff3f40125 100755
--- a/SickBeard.py
+++ b/SickBeard.py
@@ -33,7 +33,6 @@ if sys.version_info < (2, 6):
 
 try:
     import Cheetah
-
     if Cheetah.Version[0] != '2':
         raise ValueError
 except ValueError:
@@ -45,7 +44,7 @@ except:
 
 import os
 
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib')))
 
 # We only need this for compiling an EXE and I will just always do that on 2.6+
 if sys.hexversion >= 0x020600F0:
@@ -256,36 +255,22 @@ class SickRage(object):
                 raise SystemExit(
                     "Config file root dir '" + os.path.dirname(sickbeard.CONFIG_FILE) + "' must be writeable.")
 
+        os.chdir(sickbeard.DATA_DIR)
+
         # Check if we need to perform a restore first
         restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
-        if os.path.exists(restoreDir):
+        if self.consoleLogging and os.path.exists(restoreDir):
             if self.restore(restoreDir, sickbeard.DATA_DIR):
-                logger.log(u"Restore successful...")
+                sys.stdout.write("Restore successful...\n")
             else:
-                logger.log(u"Restore FAILED!", logger.ERROR)
-
-        os.chdir(sickbeard.DATA_DIR)
+                sys.stdout.write("Restore FAILED!\n")
 
         # Load the config and publish it to the sickbeard package
-        if not os.path.isfile(sickbeard.CONFIG_FILE):
-            logger.log(u"Unable to find '" + sickbeard.CONFIG_FILE + "' , all settings will be default!", logger.ERROR)
+        if self.consoleLogging and not os.path.isfile(sickbeard.CONFIG_FILE):
+            sys.stdout.write("Unable to find '" + sickbeard.CONFIG_FILE + "' , all settings will be default!" + "\n")
 
         sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
 
-        CUR_DB_VERSION = db.DBConnection().checkDBVersion()
-
-        if CUR_DB_VERSION > 0:
-            if CUR_DB_VERSION < MIN_DB_VERSION:
-                raise SystemExit("Your database version (" + str(
-                    CUR_DB_VERSION) + ") is too old to migrate from with this version of SickRage (" + str(
-                    MIN_DB_VERSION) + ").\n" + \
-                                 "Upgrade using a previous version of SB first, or start with no database file to begin fresh.")
-            if CUR_DB_VERSION > MAX_DB_VERSION:
-                raise SystemExit("Your database version (" + str(
-                    CUR_DB_VERSION) + ") has been incremented past what this version of SickRage supports (" + str(
-                    MAX_DB_VERSION) + ").\n" + \
-                                 "If you have used other forks of SB, your database may be unusable due to their modifications.")
-
         # Initialize the config and our threads
         sickbeard.initialize(consoleLogging=self.consoleLogging)
 
@@ -343,7 +328,7 @@ class SickRage(object):
                        logger.ERROR)
             if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon:
                 logger.log(u"Launching browser and exiting", logger.ERROR)
-                sickbeard.launchBrowser(self.startPort)
+                sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort, sickbeard.WEB_ROOT)
             os._exit(1)
 
         if self.consoleLogging:
@@ -368,7 +353,7 @@ class SickRage(object):
 
         # Launch browser
         if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
-            sickbeard.launchBrowser(self.startPort)
+            sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort, sickbeard.WEB_ROOT)
 
         # main loop
         while (True):
@@ -517,7 +502,6 @@ class SickRage(object):
                     if '--nolaunch' not in popen_list:
                         popen_list += ['--nolaunch']
                     logger.log(u"Restarting SickRage with " + str(popen_list))
-                    logger.close()
                     subprocess.Popen(popen_list, cwd=os.getcwd())
 
         # system exit
diff --git a/autoProcessTV/autoProcessTV.py b/autoProcessTV/autoProcessTV.py
index e02ead4715f55de75cfcb1dac0a2b6ea46bb5695..400efc05b0ecde540eb9f8aa2798b5a8dd384864 100755
--- a/autoProcessTV/autoProcessTV.py
+++ b/autoProcessTV/autoProcessTV.py
@@ -23,6 +23,12 @@ from __future__ import with_statement
 import os.path
 import sys
 
+try:
+    import requests
+except ImportError:
+    print ("You need to install python requests library")
+    sys.exit(1)
+
 # Try importing Python 2 modules using new names
 try:
     import ConfigParser as configparser
@@ -35,20 +41,6 @@ except ImportError:
     import urllib.request as urllib2
     from urllib.parse import urlencode
 
-# workaround for broken urllib2 in python 2.6.5: wrong credentials lead to an infinite recursion
-if sys.version_info >= (2, 6, 5) and sys.version_info < (2, 6, 6):
-    class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
-        def retry_http_basic_auth(self, host, req, realm):
-            # don't retry if auth failed
-            if req.get_header(self.auth_header, None) is not None:
-                return None
-
-            return urllib2.HTTPBasicAuthHandler.retry_http_basic_auth(self, host, req, realm)
-
-else:
-    HTTPBasicAuthHandler = urllib2.HTTPBasicAuthHandler
-
-
 def processEpisode(dir_to_process, org_NZB_name=None, status=None):
     # Default values
     host = "localhost"
@@ -125,20 +117,17 @@ def processEpisode(dir_to_process, org_NZB_name=None, status=None):
     else:
         protocol = "http://"
 
-    url = protocol + host + ":" + port + web_root + "home/postprocess/processEpisode?" + urlencode(params)
+    url = protocol + host + ":" + port + web_root + "home/postprocess/processEpisode"
+    login_url = protocol + host + ":" + port + web_root + "login"
 
     print ("Opening URL: " + url)
 
     try:
-        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
-        password_mgr.add_password(None, url, username, password)
-        handler = HTTPBasicAuthHandler(password_mgr)
-        opener = urllib2.build_opener(handler)
-        urllib2.install_opener(opener)
-
-        result = opener.open(url).readlines()
+        sess = requests.Session()
+        sess.post(login_url, data={'username': username, 'password': password}, stream=True, verify=False)
+        result = sess.get(url, params=params, stream=True, verify=False)
 
-        for line in result:
+        for line in result.iter_lines():
             if line:
                 print (line.strip())
 
diff --git a/autoProcessTV/mediaToSickbeard.py b/autoProcessTV/mediaToSickbeard.py
index 12ee3e696f144cb598c0314580dd0df2de75cfdf..ba0d3cafecb06db8af00001aeba78969add90a7b 100755
--- a/autoProcessTV/mediaToSickbeard.py
+++ b/autoProcessTV/mediaToSickbeard.py
@@ -190,18 +190,21 @@ def main():
         host = 'localhost'
     
     url = protocol + host + ":" + port + web_root + "/home/postprocess/processEpisode"
+    login_url = protocol + host + ":" + port + web_root + "/login"
     
     scriptlogger.debug("Opening URL: " + url + ' with params=' + str(params))   
     print "Opening URL: " + url + ' with params=' + str(params)
     
     try:
-        response = requests.get(url, auth=(username, password), params=params, verify=False)
+        sess = requests.Session()
+        sess.post(login_url, data={'username': username, 'password': password}, stream=True, verify=False)
+        response = sess.get(url, auth=(username, password), params=params, verify=False,  allow_redirects=False)
     except Exception, e:
         scriptlogger.error(u': Unknown exception raised when opening url: ' + str(e))
         time.sleep(3)
         sys.exit()
     
-    if response.status_code == 401:
+    if response.status_code == 302:
         scriptlogger.error(u'Invalid Sickbeard Username or Password, check your config')
         print 'Invalid Sickbeard Username or Password, check your config'
         time.sleep(3)
diff --git a/autoProcessTV/setup.py b/autoProcessTV/setup.py
index c79e4fd698e80d55849870f3d46aa7452460d0e4..2956e8c0982663709a8392821eb1606fffe3f926 100755
--- a/autoProcessTV/setup.py
+++ b/autoProcessTV/setup.py
@@ -1,13 +1,15 @@
-from distutils.core import setup
-import py2exe, sys, shutil
+from setuptools import setup
 
-sys.argv.append('py2exe')
-
-setup(
-      options = {'py2exe': {'bundle_files': 1}},
-#      windows = [{'console': "sabToSickbeard.py"}],
-      zipfile = None,
-      console = ['sabToSickbeard.py'],
-)
-
-shutil.copy('dist/sabToSickbeard.exe', '.')
+setup(name='sickrage',
+      version='3.3.2',
+      description='Automated Video File Manager',
+      url='http://github.com/SiCKRAGETV/SickRage',
+      author='echel0n',
+      author_email='sickrage.tv@gmail.com',
+      license='MIT',
+      packages=['funniest'],
+      install_requires=[
+          'requests',
+      ],
+      zip_safe=False,
+)
\ No newline at end of file
diff --git a/contributing.md b/contributing.md
index 69aa3f38b32c8b2f3f9dd10e645ccfd715cd1aed..f461ca53fc119a02cc5102e943e34239494dcd9d 100644
--- a/contributing.md
+++ b/contributing.md
@@ -1,6 +1,6 @@
 ### Questions about SickRage?
 
-To get your questions answered, please ask in the [SickRage Forum], on IRC \#sickrage pn freenode.net, or webchat.
+To get your questions answered, please ask on the [SickRage Forum](http://sickrage.tv/),  or [#sickrage](http://webchat.freenode.net/?channels=sickrage) IRC channel on irc.freenode.net
 
 # Contributing to SickRage
 
@@ -34,7 +34,7 @@ If you are new to SickRage, it is usually a much better idea to ask for help fir
 
 ### Try the latest version of SickRage
 
-Bugs in old versions of SickRage may have already been fixed. In order to avoid reporting known issues, make sure you are always testing against the latest build/source. Also, we put new code in the `dev` branch first before pushing down to the `master` branch (which is what the binary builds are built off of).
+Bugs in old versions of SickRage may have already been fixed. In order to avoid reporting known issues, make sure you are always testing against the latest build/source. Also, we put new code in the `develop` branch first before pushing down to the `master` branch (which is what the binary builds are built off of).
 
 
 ## Tips For Submitting Code
@@ -49,7 +49,7 @@ Bugs in old versions of SickRage may have already been fixed. In order to avoid
 
 Please follow these guidelines before reporting a bug:
 
-1. **Update to the latest version** &mdash; Check if you can reproduce the issue with the latest version from the `dev` branch.
+1. **Update to the latest version** &mdash; Check if you can reproduce the issue with the latest version from the `develop` branch.
 
 2. **Use the SickRage Forums search** &mdash; check if the issue has already been reported. If it has been, please comment on the existing issue.
 
@@ -58,7 +58,7 @@ Please follow these guidelines before reporting a bug:
 
 ### Feature requests
 
-Please follow the bug guidelines above for feature requests, i.e. update to the latest version and search for existing issues before posting a new request. You can submit Feature Requests in the [SickRage Forum] as well.
+Please follow the bug guidelines above for feature requests, i.e. update to the latest version and search for existing issues before posting a new request. You can submit Feature Requests in the [SickRage Forum](http://sickrage.tv/) as well.
 
 ### Pull requests
 
@@ -68,7 +68,7 @@ Please follow these guidelines before sending a pull request:
 
 1. Update your fork to the latest upstream version.
 
-2. Use the `dev` branch to base your code off of. Create a topic-branch for your work. We will not merge your 'dev' branch, or your 'master' branch, only topic branches, coming from dev are merged.
+2. Use the `develop` branch to base your code off of. Create a topic-branch for your work. We will not merge your 'dev' branch, or your 'master' branch, only topic branches, coming from dev are merged.
 
 3. Follow the coding conventions of the original repository. Do not change line endings of the existing file, as this will rewrite the file and loses history.
 
@@ -106,7 +106,7 @@ Please follow this process; it's the best way to get your work included in the p
 - Create a new topic branch to contain your feature, change, or fix:
 
    ```bash
-   git checkout -b <topic-branch-name> dev
+   git checkout -b <topic-branch-name> develop
    ```
 
 - Commit your changes in logical chunks. or your pull request is unlikely
diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css
index 65c75edebea5118ce7aa12d607bd618388a47fc3..aa102da757ddb0e4d21c8b29739cdec3c640d7db 100644
--- a/gui/slick/css/dark.css
+++ b/gui/slick/css/dark.css
@@ -452,7 +452,7 @@ inc_top.tmpl
 	background-position: -357px 0px;
 }
 
-.menu-icon-xbmc {
+.menu-icon-kodi {
 	background-position: -378px 0px;
 }
 
@@ -486,7 +486,7 @@ inc_top.tmpl
 	background-position: -294px 0px;
 }
 
-.submenu-icon-xbmc {
+.submenu-icon-kodi {
 	background-position: -378px 0px;
 }
 
diff --git a/gui/slick/css/lib/bootstrap.css b/gui/slick/css/lib/bootstrap.css
index 3daf57ec1e59fb925e7a0824a5b0cf38ac495c4e..adaaf82dc9049ece4eb2599460893625bb6ee050 100644
--- a/gui/slick/css/lib/bootstrap.css
+++ b/gui/slick/css/lib/bootstrap.css
@@ -1108,7 +1108,7 @@ p {
   font-weight: 300;
   line-height: 1.4;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .lead {
     font-size: 21px;
   }
@@ -1257,7 +1257,7 @@ dt {
 dd {
   margin-left: 0;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .dl-horizontal dt {
     float: left;
     width: 160px;
@@ -1396,12 +1396,12 @@ pre code {
   margin-right: auto;
   margin-left: auto;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .container {
     width: 750px;
   }
 }
-@media (min-width: 992px) {
+@media (min-width: 1011px) {
   .container {
     width: 970px;
   }
@@ -1583,7 +1583,7 @@ pre code {
 .col-xs-offset-0 {
   margin-left: 0;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
     float: left;
   }
@@ -1741,7 +1741,7 @@ pre code {
     margin-left: 0;
   }
 }
-@media (min-width: 992px) {
+@media (min-width: 1011px) {
   .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
     float: left;
   }
@@ -2636,7 +2636,7 @@ select[multiple].input-lg {
   margin-bottom: 10px;
   color: #737373;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .form-inline .form-group {
     display: inline-block;
     margin-bottom: 0;
@@ -2699,7 +2699,7 @@ select[multiple].input-lg {
   margin-right: -15px;
   margin-left: -15px;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .form-horizontal .control-label {
     padding-top: 7px;
     margin-bottom: 0;
@@ -2710,12 +2710,12 @@ select[multiple].input-lg {
   top: 0;
   right: 15px;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .form-horizontal .form-group-lg .control-label {
     padding-top: 14.3px;
   }
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .form-horizontal .form-group-sm .control-label {
     padding-top: 6px;
   }
@@ -3239,7 +3239,7 @@ tbody.collapse.in {
   bottom: 100%;
   margin-bottom: 1px;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-right .dropdown-menu {
     right: 0;
     left: auto;
@@ -3658,7 +3658,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   top: auto;
   left: auto;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .nav-tabs.nav-justified > li {
     display: table-cell;
     width: 1%;
@@ -3676,7 +3676,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
 .nav-tabs.nav-justified > .active > a:focus {
   border: 1px solid #ddd;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .nav-tabs.nav-justified > li > a {
     border-bottom: 1px solid #ddd;
     border-radius: 4px 4px 0 0;
@@ -3723,7 +3723,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   top: auto;
   left: auto;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .nav-justified > li {
     display: table-cell;
     width: 1%;
@@ -3744,7 +3744,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
 .nav-tabs-justified > .active > a:focus {
   border: 1px solid #ddd;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .nav-tabs-justified > li > a {
     border-bottom: 1px solid #ddd;
     border-radius: 4px 4px 0 0;
@@ -3772,12 +3772,12 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   margin-bottom: 20px;
   border: 1px solid transparent;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar {
     border-radius: 4px;
   }
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-header {
     float: left;
   }
@@ -3794,7 +3794,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
 .navbar-collapse.in {
   overflow-y: auto;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-collapse {
     width: auto;
     border-top: 0;
@@ -3834,7 +3834,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   margin-right: -15px;
   margin-left: -15px;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .container > .navbar-header,
   .container-fluid > .navbar-header,
   .container > .navbar-collapse,
@@ -3847,7 +3847,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   z-index: 1000;
   border-width: 0 0 1px;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-static-top {
     border-radius: 0;
   }
@@ -3859,7 +3859,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   left: 0;
   z-index: 1030;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-fixed-top,
   .navbar-fixed-bottom {
     border-radius: 0;
@@ -3885,7 +3885,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
 .navbar-brand:focus {
   text-decoration: none;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar > .container .navbar-brand,
   .navbar > .container-fluid .navbar-brand {
     margin-left: -15px;
@@ -3915,7 +3915,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
 .navbar-toggle .icon-bar + .icon-bar {
   margin-top: 4px;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-toggle {
     display: none;
   }
@@ -3951,7 +3951,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
     background-image: none;
   }
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-nav {
     float: left;
     margin: 0;
@@ -3967,7 +3967,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
     margin-right: -15px;
   }
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-left {
     float: left !important;
   }
@@ -3986,7 +3986,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
           box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-form .form-group {
     display: inline-block;
     margin-bottom: 0;
@@ -4038,7 +4038,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
     margin-bottom: 5px;
   }
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-form {
     width: auto;
     padding-top: 0;
@@ -4078,7 +4078,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   margin-top: 15px;
   margin-bottom: 15px;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .navbar-text {
     float: left;
     margin-right: 15px;
@@ -4565,7 +4565,7 @@ a.list-group-item.active > .badge,
 .jumbotron .container {
   max-width: 100%;
 }
-@media screen and (min-width: 768px) {
+@media screen and (min-width: 1010px) {
   .jumbotron {
     padding-top: 48px;
     padding-bottom: 48px;
@@ -5473,7 +5473,7 @@ button.close {
   height: 50px;
   overflow: scroll;
 }
-@media (min-width: 768px) {
+@media (min-width: 1010px) {
   .modal-dialog {
     width: 600px;
     margin: 30px auto;
@@ -5486,7 +5486,7 @@ button.close {
     width: 300px;
   }
 }
-@media (min-width: 992px) {
+@media (min-width: 1011px) {
   .modal-lg {
     width: 900px;
   }
@@ -5873,7 +5873,7 @@ button.close {
 .carousel-caption .btn {
   text-shadow: none;
 }
-@media screen and (min-width: 768px) {
+@media screen and (min-width: 1010px) {
   .carousel-control .glyphicon-chevron-left,
   .carousel-control .glyphicon-chevron-right,
   .carousel-control .icon-prev,
@@ -6040,7 +6040,7 @@ button.close {
     display: inline-block !important;
   }
 }
-@media (min-width: 768px) and (max-width: 991px) {
+@media (min-width: 1010px) and (max-width: 1012px) {
   .visible-sm {
     display: block !important;
   }
@@ -6055,22 +6055,22 @@ button.close {
     display: table-cell !important;
   }
 }
-@media (min-width: 768px) and (max-width: 991px) {
+@media (min-width: 1010px) and (max-width: 1012px) {
   .visible-sm-block {
     display: block !important;
   }
 }
-@media (min-width: 768px) and (max-width: 991px) {
+@media (min-width: 1010px) and (max-width: 1012px) {
   .visible-sm-inline {
     display: inline !important;
   }
 }
-@media (min-width: 768px) and (max-width: 991px) {
+@media (min-width: 1010px) and (max-width: 1012px) {
   .visible-sm-inline-block {
     display: inline-block !important;
   }
 }
-@media (min-width: 992px) and (max-width: 1199px) {
+@media (min-width: 1011px) and (max-width: 1199px) {
   .visible-md {
     display: block !important;
   }
@@ -6085,17 +6085,17 @@ button.close {
     display: table-cell !important;
   }
 }
-@media (min-width: 992px) and (max-width: 1199px) {
+@media (min-width: 1011px) and (max-width: 1199px) {
   .visible-md-block {
     display: block !important;
   }
 }
-@media (min-width: 992px) and (max-width: 1199px) {
+@media (min-width: 1011px) and (max-width: 1199px) {
   .visible-md-inline {
     display: inline !important;
   }
 }
-@media (min-width: 992px) and (max-width: 1199px) {
+@media (min-width: 1011px) and (max-width: 1199px) {
   .visible-md-inline-block {
     display: inline-block !important;
   }
@@ -6135,12 +6135,12 @@ button.close {
     display: none !important;
   }
 }
-@media (min-width: 768px) and (max-width: 991px) {
+@media (min-width: 1010px) and (max-width: 1012px) {
   .hidden-sm {
     display: none !important;
   }
 }
-@media (min-width: 992px) and (max-width: 1199px) {
+@media (min-width: 1011px) and (max-width: 1199px) {
   .hidden-md {
     display: none !important;
   }
diff --git a/gui/slick/css/light.css b/gui/slick/css/light.css
index 96f3fa95faa220af42a48b790490e4f674b23979..809a7c412917993f8dbb25b0d37f5d025324ea5b 100644
--- a/gui/slick/css/light.css
+++ b/gui/slick/css/light.css
@@ -434,7 +434,7 @@ inc_top.tmpl
 	background-position: -357px 0px;
 }
 
-.menu-icon-xbmc {
+.menu-icon-kodi {
 	background-position: -378px 0px;
 }
 
@@ -468,7 +468,7 @@ inc_top.tmpl
 	background-position: -294px 0px;
 }
 
-.submenu-icon-xbmc {
+.submenu-icon-kodi {
 	background-position: -378px 0px;
 }
 
diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css
index c6808db9a1f0146c729cdacfd8e330707865c299..75b963be1f5fb1dd359ddbac031f538cffa140a2 100644
--- a/gui/slick/css/style.css
+++ b/gui/slick/css/style.css
@@ -434,7 +434,7 @@ inc_top.tmpl
 	background-position: -357px 0px;
 }
 
-.menu-icon-xbmc {
+.menu-icon-kodi {
 	background-position: -378px 0px;
 }
 
@@ -468,7 +468,7 @@ inc_top.tmpl
 	background-position: -294px 0px;
 }
 
-.submenu-icon-xbmc {
+.submenu-icon-kodi {
 	background-position: -378px 0px;
 }
 
@@ -3133,4 +3133,74 @@ pnotify.css
 .ui-pnotify-closer {
     margin-top: -12px;
     margin-right: -10px;
-}
\ No newline at end of file
+}
+
+/* =======================================================================
+login.css
+========================================================================== */
+
+.login {
+	display: block;
+}
+
+	.login h1 {
+		padding: 0 0 10px;
+		font-size: 60px;
+		font-family: Lobster;
+		font-weight: normal;
+	}
+
+	.login form {
+		padding: 0;
+		height: 300px;
+		width: 400px;
+		position: fixed;
+		left: 50%;
+		top: 50%;
+		margin: -200px 0 0 -200px;
+	}
+		@media all and (max-width: 480px) {
+
+			.login form {
+				padding: 0;
+				height: 300px;
+				width: 90%;
+				position: absolute;
+				left: 5%;
+				top: 10px;
+				margin: 0;
+			}
+
+		}
+
+	.login .ctrlHolder {
+		padding: 0;
+		margin: 0 0 20px;
+	}
+		.login .ctrlHolder:hover {
+			background: none;
+		}
+
+	.login input[type=text],
+	.login input[type=password] {
+		width: 100% !important;
+		font-size: 25px;
+		padding: 14px !important;
+	}
+
+	.login .remember_me {
+		font-size: 15px;
+		float: left;
+		width: 150px;
+		padding: 20px 0;
+	}
+
+		.login .remember_me .check {
+			margin: 5px 5px 0 0;
+		}
+
+	.login .button {
+		font-size: 25px;
+		padding: 20px;
+		float: right;
+	}
\ No newline at end of file
diff --git a/gui/slick/images/network/crackle.png b/gui/slick/images/network/crackle.png
new file mode 100644
index 0000000000000000000000000000000000000000..d46a69572152f949b4e5bebe99865f2fa861127b
Binary files /dev/null and b/gui/slick/images/network/crackle.png differ
diff --git a/gui/slick/images/network/el rey network.png b/gui/slick/images/network/el rey network.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab136379ab04e97aa26d8e6743fbee6192b863f5
Binary files /dev/null and b/gui/slick/images/network/el rey network.png differ
diff --git a/gui/slick/images/network/radio canada.png b/gui/slick/images/network/radio canada.png
new file mode 100644
index 0000000000000000000000000000000000000000..f91bb09ab3e94b294cddd5d2f62dd2c124a67fbb
Binary files /dev/null and b/gui/slick/images/network/radio canada.png differ
diff --git a/gui/slick/images/network/the hub.png b/gui/slick/images/network/the hub.png
new file mode 100644
index 0000000000000000000000000000000000000000..8091a3fe9cdad4f7d839ee5153b637ae442532d7
Binary files /dev/null and b/gui/slick/images/network/the hub.png differ
diff --git a/gui/slick/images/notifiers/kodi.png b/gui/slick/images/notifiers/kodi.png
new file mode 100644
index 0000000000000000000000000000000000000000..edc94cf52f589bafe3f08fa2dcac21bc69010c11
Binary files /dev/null and b/gui/slick/images/notifiers/kodi.png differ
diff --git a/gui/slick/images/notifiers/xbmc.png b/gui/slick/images/notifiers/xbmc.png
deleted file mode 100644
index 16bc1a03e91fe47c57450f7f72f7910e6ad59825..0000000000000000000000000000000000000000
Binary files a/gui/slick/images/notifiers/xbmc.png and /dev/null differ
diff --git a/gui/slick/interfaces/default/apiBuilder.tmpl b/gui/slick/interfaces/default/apiBuilder.tmpl
index 4ff364f3beb8743b8606acdd545d2f1e548f383c..6d1ea673982de960c6352a1cf7d46f96daaef328 100644
--- a/gui/slick/interfaces/default/apiBuilder.tmpl
+++ b/gui/slick/interfaces/default/apiBuilder.tmpl
@@ -1,25 +1,28 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"> 
-<head>
-<title>API Builder</title>
-<script type="text/javascript" charset="utf-8">
-<!--
-sbRoot = "$sbRoot";
-//-->
-</script>
-<script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?$sbPID"></script>
-<script type="text/javascript" src="$sbRoot/js/apibuilder.js?$sbPID"></script>
-
-<style type="text/css">
-<!--
-#apibuilder select { padding: 2px 2px 2px 6px; display: block; float: left; margin: auto 8px 4px auto;
-}
-#apibuilder select option { padding: 1px 6px; line-height: 1.2em; }
-#apibuilder .disabled { color: #ccc; }
-#apibuilder .action { background-color: #efefef; }
--->
-</style>
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>SickRage - API Builder</title>
+        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+        <meta name="viewport" content="width=device-width">
+        <meta name="robots" content="noindex">
+
+        <script type="text/javascript" charset="utf-8">
+        <!--
+            sbRoot = "$sbRoot";
+        //-->
+        </script>
+        <script type="text/javascript" src="$sbRoot/js/lib/jquery-1.8.3.min.js?$sbPID"></script>
+        <script type="text/javascript" src="$sbRoot/js/apibuilder.js?$sbPID"></script>
+
+        <style type="text/css">
+        <!--
+            #apibuilder select { padding: 2px 2px 2px 6px; display: block; float: left; margin: auto 8px 4px auto; }
+            #apibuilder select option { padding: 1px 6px; line-height: 1.2em; }
+            #apibuilder .disabled { color: #ccc; }
+            #apibuilder .action { background-color: #efefef; }
+        -->
+        </style>
 
 <script type="text/javascript">
 var hide_empty_list=true; 
@@ -27,22 +30,20 @@ var disable_empty_list=true;
 
 addListGroup("api", "Command");
 
-addOption("Command", "SickBeard", "?cmd=sb", 1); //make default
-addList("Command", "SickBeard.AddRootDir", "?cmd=sb.addrootdir", "sb.addrootdir", "", "", "action");
-addOption("Command", "SickBeard.CheckScheduler", "?cmd=sb.checkscheduler", "", "", "action");
-addList("Command", "SickBeard.DeleteRootDir", "?cmd=sb.deleterootdir", "sb.deleterootdir", "", "", "action");
-addOption("Command", "SickBeard.ForceSearch", "?cmd=sb.forcesearch", "", "", "action");
-addOption("Command", "SickBeard.GetDefaults", "?cmd=sb.getdefaults", "", "", "action");
-addOption("Command", "SickBeard.GetMessages", "?cmd=sb.getmessages", "", "", "action");
-addOption("Command", "SickBeard.GetRootDirs", "?cmd=sb.getrootdirs", "", "", "action");
-addList("Command", "SickBeard.PauseBacklog", "?cmd=sb.pausebacklog", "sb.pausebacklog", "", "", "action");
-addOption("Command", "SickBeard.Ping", "?cmd=sb.ping", "", "", "action");
-addOption("Command", "SickBeard.Restart", "?cmd=sb.restart", "", "", "action");
-addList("Command", "SickBeard.SearchAllIndexers", "?cmd=sb.searchindexers", "sb.searchindexers", "", "", "action");
-addList("Command", "SickBeard.SearchTVDB", "?cmd=sb.searchtvdb&indexer=1", "sb.searchindexers", "", "", "action");
-addList("Command", "SickBeard.SearchTVRage", "?cmd=sb.searchtvrage&indexer=2", "sb.searchindexers", "", "", "action");
-addList("Command", "SickBeard.SetDefaults", "?cmd=sb.setdefaults", "sb.setdefaults", "", "", "action");
-addOption("Command", "SickBeard.Shutdown", "?cmd=sb.shutdown", "", "", "action");
+addOption("Command", "SickRage", "?cmd=sb", 1); //make default
+addList("Command", "SickRage.AddRootDir", "?cmd=sb.addrootdir", "sb.addrootdir", "", "", "action");
+addOption("Command", "SickRage.CheckScheduler", "?cmd=sb.checkscheduler", "", "", "action");
+addList("Command", "SickRage.DeleteRootDir", "?cmd=sb.deleterootdir", "sb.deleterootdir", "", "", "action");
+addOption("Command", "SickRage.ForceSearch", "?cmd=sb.forcesearch", "", "", "action");
+addOption("Command", "SickRage.GetDefaults", "?cmd=sb.getdefaults", "", "", "action");
+addOption("Command", "SickRage.GetMessages", "?cmd=sb.getmessages", "", "", "action");
+addOption("Command", "SickRage.GetRootDirs", "?cmd=sb.getrootdirs", "", "", "action");
+addList("Command", "SickRage.PauseBacklog", "?cmd=sb.pausebacklog", "sb.pausebacklog", "", "", "action");
+addOption("Command", "SickRage.Ping", "?cmd=sb.ping", "", "", "action");
+addOption("Command", "SickRage.Restart", "?cmd=sb.restart", "", "", "action");
+addList("Command", "SickRage.searchindexers", "?cmd=sb.searchindexers", "sb.searchindexers", "", "", "action");
+addList("Command", "SickRage.SetDefaults", "?cmd=sb.setdefaults", "sb.setdefaults", "", "", "action");
+addOption("Command", "SickRage.Shutdown", "?cmd=sb.shutdown", "", "", "action");
 addList("Command", "Coming Episodes", "?cmd=future", "future");
 addList("Command", "Episode", "?cmd=episode", "episode");
 addList("Command", "Episode.Search", "?cmd=episode.search", "episode.search", "", "", "action");
@@ -51,16 +52,12 @@ addList("Command", "Scene Exceptions", "?cmd=exceptions", "exceptions");
 addList("Command", "History", "?cmd=history", "history");
 addOption("Command", "History.Clear", "?cmd=history.clear", "", "", "action");
 addOption("Command", "History.Trim", "?cmd=history.trim", "", "", "action");
-addList("Command", "Failed", "?cmd=failed", "failed");
-addOption("Command", "Backlog", "?cmd=backlog");
-addList("Command", "PostProcess", "?cmd=postprocess", "postprocess", "", "","action");
-
 addList("Command", "Logs", "?cmd=logs", "logs");
 addList("Command", "Show", "?cmd=show", "indexerid");
 addList("Command", "Show.AddExisting", "?cmd=show.addexisting", "show.addexisting", "", "", "action");
 addList("Command", "Show.AddNew", "?cmd=show.addnew", "show.addnew", "", "", "action");
 addList("Command", "Show.Cache", "?cmd=show.cache", "indexerid", "", "", "action");
-addList("Command", "Show.Delete", "?cmd=show.delete", "indexerid", "", "", "action");
+addList("Command", "Show.Delete", "?cmd=show.delete", "show.delete", "", "", "action");
 addList("Command", "Show.GetBanner", "?cmd=show.getbanner", "indexerid", "", "", "action");
 addList("Command", "Show.GetPoster", "?cmd=show.getposter", "indexerid", "", "", "action");
 addList("Command", "Show.GetQuality", "?cmd=show.getquality", "indexerid", "", "", "action");
@@ -85,14 +82,6 @@ addOption("logs", "Info", "&min_level=info");
 addOption("logs", "Warning", "&min_level=warning");
 addOption("logs", "Error", "&min_level=error");
 
-addOption("postprocess", "Optional Param", "", 1);
-addOption("postprocess", "C:\\PATH\\TO\\DIR", "&path=C:\\Temp");
-addOption("postprocess", "return_data", "&return_data=1");
-addOption("postprocess", "force_replace", "&force_replace=1");
-addOption("postprocess", "is_priority", "&is_priority=1");
-addOption("postprocess", "process_method", "&process_method=copy");
-addOption("postprocess", "type", "&type=manual")
-
 addOption("sb.setdefaults", "Optional Param", "", 1);
 addList("sb.setdefaults", "Exclude Paused Shows on ComingEps", "&future_show_paused=0", "sb.setdefaults-status");
 addList("sb.setdefaults", "Include Paused Shows on ComingEps", "&future_show_paused=1", "sb.setdefaults-status");
@@ -104,14 +93,14 @@ addList("sb.setdefaults-status", "Archived", "&status=archived", "sb.setdefaults
 addList("sb.setdefaults-status", "Ignored", "&status=ignored", "sb.setdefaults-opt");
 
 addOption("sb.setdefaults-opt", "Optional Param", "", 1);
-addList("sb.setdefaults-opt", "No Season Folder", "&season_folder=0", "quality");
-addList("sb.setdefaults-opt", "Use Season Folder", "&season_folder=1", "quality");
+addList("sb.setdefaults-opt", "Flatten (No Season Folder)", "&flatten_folders=1", "quality");
+addList("sb.setdefaults-opt", "Use Season Folder", "&flatten_folders=0", "quality");
 
 addOption("shows", "Optional Param", "", 1);
 addOption("shows", "Show Only Paused", "&paused=1");
 addOption("shows", "Show Only Not Paused", "&paused=0");
 addOption("shows", "Sort by Show Name", "&sort=name");
-addOption("shows", "Sort by TVDB ID", "&sort=id");
+addOption("shows", "Sort by INDEXER ID", "&sort=id");
 
 addList("show.addexisting", "C:\\temp\\show1", "&location=C:\\temp\\show1", "show.addexisting-indexerid");
 addList("show.addexisting", "D:\\Temp\\show2", "&location=D:\\Temp\\show2", "show.addexisting-indexerid");
@@ -122,8 +111,8 @@ addList("show.addexisting-indexerid", "101501 (Ancient Aliens)", "&indexerid=101
 addList("show.addexisting-indexerid", "80348 (Chuck)", "&indexerid=80348", "show.addexisting-opt");
 
 addOption("show.addexisting-opt", "Optional Param", "", 1);
-addList("show.addexisting-opt", "No Season Folder", "&season_folder=0", "quality");
-addList("show.addexisting-opt", "Use Season Folder", "&season_folder=1", "quality");
+addList("show.addexisting-opt", "Flatten (No Season Folder)", "&flatten_folders=1", "quality");
+addList("show.addexisting-opt", "Use Season Folder", "&flatten_folders=0", "quality");
 
 addList("show.addnew", "101501 (Ancient Aliens)", "&indexerid=101501", "show.addnew-loc");
 addList("show.addnew", "80348 (Chuck)", "&indexerid=80348", "show.addnew-loc");
@@ -141,8 +130,8 @@ addList("show.addnew-status", "Archived", "&status=archived", "show.addnew-opt")
 addList("show.addnew-status", "Ignored", "&status=ignored", "show.addnew-opt");
 
 addOption("show.addnew-opt", "Optional Param", "", 1);
-addList("show.addnew-opt", "No Season Folder", "&season_folder=0", "quality");
-addList("show.addnew-opt", "Use Season Folder", "&season_folder=1", "quality");
+addList("show.addnew-opt", "Flatten (No Season Folder)", "&flatten_folders=1", "quality");
+addList("show.addnew-opt", "Use Season Folder", "&flatten_folders=0", "quality");
 
 addOptGroup("sb.searchindexers", "Search by Name");
 addList("sb.searchindexers", "Lost", "&name=Lost", "sb.searchindexers-lang");
@@ -279,15 +268,25 @@ addList("episode.setstatus", "$curShow.name", "&indexerid=$curShow.indexerid", "
 
 // build out each show's season+episode list for episode.setstatus cmd
 #for $curShow in $episodeSQLResults:
+    #set $curSeason = -1
     #for $curShowSeason in $episodeSQLResults[$curShow]:
+        #if $curShowSeason.season != $curSeason and $curShowSeason.season != 0:
+            // insert just the season as the ep number is now optional
+            addList("episode.setstatus-$curShow", "Season $curShowSeason.season", "&season=$curShowSeason.season", "episode-status-$curShow");
+        #end if
+        #set $curSeason = int($curShowSeason.season)
 addList("episode.setstatus-$curShow", "$curShowSeason.season x $curShowSeason.episode", "&season=$curShowSeason.season&episode=$curShowSeason.episode", "episode-status-$curShow");
     #end for
-addOption("episode-status-$curShow", "Wanted", "&status=wanted");
-addOption("episode-status-$curShow", "Skipped", "&status=skipped");
-addOption("episode-status-$curShow", "Archived", "&status=archived");
-addOption("episode-status-$curShow", "Ignored", "&status=ignored");
+addList("episode-status-$curShow", "Wanted", "&status=wanted", "force");
+addList("episode-status-$curShow", "Skipped", "&status=skipped", "force");
+addList("episode-status-$curShow", "Archived", "&status=archived", "force");
+addList("episode-status-$curShow", "Ignored", "&status=ignored", "force");
 #end for
 
+addOption("force", "Optional Param", "", 1);
+addOption("force", "Replace Downloaded EP", "&force=1");
+addOption("force", "Skip Downloaded EP", "&force=0");
+
 addOption("future", "Optional Param", "", 1);
 addList("future", "Sort by Date", "&sort=date", "future-type");
 addList("future", "Sort by Network", "&sort=network", "future-type");
@@ -323,15 +322,6 @@ addOption("history-limit", "Optional Param", "", 1);
 addOption("history-limit", "Show Only Downloaded", "&type=downloaded");
 addOption("history-limit", "Show Only Snatched", "&type=snatched");
 
-addOption("failed", "Optional Param", "", 1);
-//addOptGroup("failed", "Limit Results");
-addList("failed", "Limit Results (2)", "&limit=2", "failed-limit");
-addList("failed", "Limit Results (25)", "&limit=25", "failed-limit");
-addList("failed", "Limit Results (50)", "&limit=50", "failed-limit");
-//endOptGroup("failed");
-
-addOption("failed-limit", "Optional Param", "", 1);
-
 addOption("exceptions", "Optional Param", "", 1);
 #for $curShow in $sortedShowList:
 addOption("exceptions", "$curShow.name", "&indexerid=$curShow.indexerid");
@@ -360,6 +350,12 @@ addOption("show.pause-opt", "Optional Param", "", 1);
 addOption("show.pause-opt", "Unpause", "&pause=0");
 addOption("show.pause-opt", "Pause", "&pause=1");
 
+#for $curShow in $sortedShowList:
+addList("show.delete", "$curShow.name", "&indexerid=$curShow.indexerid", "show.delete-opt");
+#end for
+addOption("show.delete-opt", "Remove Files", "&removefiles=1");
+addOption("show.delete-opt", "Don't Remove Files", "&removefiles=0");
+
 </script>
 </head>
 
@@ -386,8 +382,8 @@ addOption("show.pause-opt", "Pause", "&pause=1");
             <select name="sixthlevel"><option></option></select>
             <select name="seventhlevel"><option></option></select>
             <div style="float: left; ">
-                <input class="btn" type="button" value="Reset" onclick="resetListGroup('api',1)" />
-                <input class="btn" type="button" value="Go" onclick="goListGroup(this.form['apikey'].value, this.form['seventhlevel'].value, this.form['sixthlevel'].value, this.form['fifthlevel'].value, this.form['forthlevel'].value, this.form['thirdlevel'].value, this.form['secondlevel'].value, this.form['firstlevel'].value)" />
+                <input type="button" value="Reset" onclick="resetListGroup('api',1)" />
+                <input type="button" value="Go" onclick="goListGroup(this.form['apikey'].value, this.form['seventhlevel'].value, this.form['sixthlevel'].value, this.form['fifthlevel'].value, this.form['forthlevel'].value, this.form['thirdlevel'].value, this.form['secondlevel'].value, this.form['firstlevel'].value)" />
             </div>
         </td>
     </tr>
@@ -398,4 +394,4 @@ addOption("show.pause-opt", "Pause", "&pause=1");
 
 </body>
 
-</html>
+</html>
\ No newline at end of file
diff --git a/gui/slick/interfaces/default/config_notifications.tmpl b/gui/slick/interfaces/default/config_notifications.tmpl
index acbf68b795626daa790029c032b4e2715e69dafe..4d3dd470fc30d9ee5f2717f70bef7247d192451b 100644
--- a/gui/slick/interfaces/default/config_notifications.tmpl
+++ b/gui/slick/interfaces/default/config_notifications.tmpl
@@ -32,93 +32,93 @@
                 <div class="component-group">
                 
                     <div class="component-group-desc">
-                        <img class="notifier-icon" src="$sbRoot/images/notifiers/xbmc.png" alt="" title="XBMC" />
-                        <h3><a href="<%= anon_url('http://kodi.tv/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">XBMC</a></h3>
+                        <img class="notifier-icon" src="$sbRoot/images/notifiers/kodi.png" alt="" title="KODI" />
+                        <h3><a href="<%= anon_url('http://kodi.tv/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">KODI</a></h3>
                         <p>A free and open source cross-platform media center and home entertainment system software with a 10-foot user interface designed for the living-room TV.</p>
                     </div>
                     <fieldset class="component-group-list">
                         <div class="field-pair">
-                            <label class="cleafix" for="use_xbmc">
+                            <label class="cleafix" for="use_kodi">
                                 <span class="component-title">Enable</span>
                                 <span class="component-desc">
-                                    <input type="checkbox" class="enabler" name="use_xbmc" id="use_xbmc" #if $sickbeard.USE_XBMC then "checked=\"checked\"" else ""# />
-                                    <p>should SickRage send XBMC commands ?<p>
+                                    <input type="checkbox" class="enabler" name="use_kodi" id="use_kodi" #if $sickbeard.USE_KODI then "checked=\"checked\"" else ""# />
+                                    <p>should SickRage send KODI commands ?<p>
                                 </span>
                             </label>
                         </div>
 
-                        <div id="content_use_xbmc">
+                        <div id="content_use_kodi">
                             <div class="field-pair">
-                                <label for="xbmc_always_on">
+                                <label for="kodi_always_on">
                                     <span class="component-title">Always on</span>
                                     <span class="component-desc">
-                                        <input type="checkbox" name="xbmc_always_on" id="xbmc_always_on" #if $sickbeard.XBMC_ALWAYS_ON then "checked=\"checked\"" else ""# />
+                                        <input type="checkbox" name="kodi_always_on" id="kodi_always_on" #if $sickbeard.KODI_ALWAYS_ON then "checked=\"checked\"" else ""# />
                                         <p>log errors when unreachable ?</p>
                                     </span>
                                 </label>
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_notify_onsnatch">
+                                <label for="kodi_notify_onsnatch">
                                     <span class="component-title">Notify on snatch</span>
                                     <span class="component-desc">
-                                        <input type="checkbox" name="xbmc_notify_onsnatch" id="xbmc_notify_onsnatch" #if $sickbeard.XBMC_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
+                                        <input type="checkbox" name="kodi_notify_onsnatch" id="kodi_notify_onsnatch" #if $sickbeard.KODI_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
                                         <p>send a notification when a download starts ?</p>
                                     </span>
                                 </label>
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_notify_ondownload">
+                                <label for="kodi_notify_ondownload">
                                     <span class="component-title">Notify on download</span>
                                     <span class="component-desc">
-                                        <input type="checkbox" name="xbmc_notify_ondownload" id="xbmc_notify_ondownload" #if $sickbeard.XBMC_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# />
+                                        <input type="checkbox" name="kodi_notify_ondownload" id="kodi_notify_ondownload" #if $sickbeard.KODI_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# />
                                         <p>send a notification when a download finishes ?</p>
                                     </span>
                                 </label>
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_notify_onsubtitledownload">
+                                <label for="kodi_notify_onsubtitledownload">
                                     <span class="component-title">Notify on subtitle download</span>
                                     <span class="component-desc">
-                                        <input type="checkbox" name="xbmc_notify_onsubtitledownload" id="xbmc_notify_onsubtitledownload" #if $sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD then "checked=\"checked\"" else ""# />
+                                        <input type="checkbox" name="kodi_notify_onsubtitledownload" id="kodi_notify_onsubtitledownload" #if $sickbeard.KODI_NOTIFY_ONSUBTITLEDOWNLOAD then "checked=\"checked\"" else ""# />
                                         <p>send a notification when subtitles are downloaded ?</p>
                                     </span>
                                 </label>
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_update_library">
+                                <label for="kodi_update_library">
                                     <span class="component-title">Update library</span>
                                     <span class="component-desc">
-                                        <input type="checkbox" name="xbmc_update_library" id="xbmc_update_library" #if $sickbeard.XBMC_UPDATE_LIBRARY then "checked=\"checked\"" else ""# />
-                                        <p>update XBMC library when a download finishes ?</p>
+                                        <input type="checkbox" name="kodi_update_library" id="kodi_update_library" #if $sickbeard.KODI_UPDATE_LIBRARY then "checked=\"checked\"" else ""# />
+                                        <p>update KODI library when a download finishes ?</p>
                                     </span>
                                 </label>
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_update_full">
+                                <label for="kodi_update_full">
                                     <span class="component-title">Full library update</span>
                                     <span class="component-desc">
-                                        <input type="checkbox" name="xbmc_update_full" id="xbmc_update_full" #if $sickbeard.XBMC_UPDATE_FULL then "checked=\"checked\"" else ""# />
+                                        <input type="checkbox" name="kodi_update_full" id="kodi_update_full" #if $sickbeard.KODI_UPDATE_FULL then "checked=\"checked\"" else ""# />
                                         <p>perform a full library update if update per-show fails ?</p>
                                     </span>
                                 </label>
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_update_onlyfirst">
+                                <label for="kodi_update_onlyfirst">
                                     <span class="component-title">Only update first host</span>
                                     <span class="component-desc">
-                                        <input type="checkbox" name="xbmc_update_onlyfirst" id="xbmc_update_onlyfirst" #if $sickbeard.XBMC_UPDATE_ONLYFIRST then "checked=\"checked\"" else ""# />
+                                        <input type="checkbox" name="kodi_update_onlyfirst" id="kodi_update_onlyfirst" #if $sickbeard.KODI_UPDATE_ONLYFIRST then "checked=\"checked\"" else ""# />
                                         <p>only send library updates to the first active host ?</p>
                                     </span>
                                 </label>
                             </div>                            
                             <div class="field-pair">
-                                <label for="xbmc_host">
-                                    <span class="component-title">XBMC IP:Port</span>
-                                    <input type="text" name="xbmc_host" id="xbmc_host" value="$sickbeard.XBMC_HOST" class="form-control input-sm input350" />
+                                <label for="kodi_host">
+                                    <span class="component-title">KODI IP:Port</span>
+                                    <input type="text" name="kodi_host" id="kodi_host" value="$sickbeard.KODI_HOST" class="form-control input-sm input350" />
                                 </label>
                                 <label>
                                     <span class="component-title">&nbsp;</span>
-                                    <span class="component-desc">host running XBMC (eg. 192.168.1.100:8080)</span>
+                                    <span class="component-desc">host running KODI (eg. 192.168.1.100:8080)</span>
                                 </label>
                                 <label>
                                     <span class="component-title">&nbsp;</span>
@@ -126,33 +126,33 @@
                                 </label>                         
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_username">
-                                    <span class="component-title">XBMC username</span>
-                                    <input type="text" name="xbmc_username" id="xbmc_username" value="$sickbeard.XBMC_USERNAME" class="form-control input-sm input250" />
+                                <label for="kodi_username">
+                                    <span class="component-title">KODI username</span>
+                                    <input type="text" name="kodi_username" id="kodi_username" value="$sickbeard.KODI_USERNAME" class="form-control input-sm input250" />
                                 </label>
                                 <label>
                                     <span class="component-title">&nbsp;</span>
-                                    <span class="component-desc">username for your XBMC server (blank for none)</span>
+                                    <span class="component-desc">username for your KODI server (blank for none)</span>
                                 </label>
                             </div>
                             <div class="field-pair">
-                                <label for="xbmc_password">
-                                    <span class="component-title">XBMC password</span>
-                                    <input type="password" name="xbmc_password" id="xbmc_password" value="$sickbeard.XBMC_PASSWORD" class="form-control input-sm input250" />
+                                <label for="kodi_password">
+                                    <span class="component-title">KODI password</span>
+                                    <input type="password" name="kodi_password" id="kodi_password" value="$sickbeard.KODI_PASSWORD" class="form-control input-sm input250" />
                                 </label>
                                 <label>
                                     <span class="component-title">&nbsp;</span>
-                                    <span class="component-desc">password for your XBMC server (blank for none)</span>
+                                    <span class="component-desc">password for your KODI server (blank for none)</span>
                                 </label>
                             </div>
-                            <div class="testNotification" id="testXBMC-result">Click below to test.</div>
-                            <input  class="btn" type="button" value="Test XBMC" id="testXBMC" />
+                            <div class="testNotification" id="testKODI-result">Click below to test.</div>
+                            <input  class="btn" type="button" value="Test KODI" id="testKODI" />
                             <input type="submit" class="config_submitter btn" value="Save Changes" />
-                        </div><!-- /content_use_xbmc //-->
+                        </div><!-- /content_use_kodi //-->
 
                     </fieldset>
                     
-                </div><!-- /xbmc component-group //-->
+                </div><!-- /kodi component-group //-->
 
 
                 <div class="component-group">
@@ -160,7 +160,7 @@
                         <img class="notifier-icon" src="$sbRoot/images/notifiers/plex.png" alt="" title="Plex Media Server" />
                         <h3><a href="<%= anon_url('http://www.plexapp.com/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">Plex Media Server</a></h3>
                         <p>Experience your media on a visually stunning, easy to use interface on your Mac connected to your TV. Your media library has never looked this good!</p>
-                        <p class="plexinfo hide">For sending notifications to Plex Home Theater (PHT) clients, use the XBMC notifier with port <b>3005</b>.</p>
+                        <p class="plexinfo hide">For sending notifications to Plex Home Theater (PHT) clients, use the KODI notifier with port <b>3005</b>.</p>
                     </div>
                     <fieldset class="component-group-list">
                         <div class="field-pair">
diff --git a/gui/slick/interfaces/default/config_providers.tmpl b/gui/slick/interfaces/default/config_providers.tmpl
index 5591d68ddf91c90450ab0093bfe9963646c9bfca..666f2ce8a6f47cf17330180e56e9273b67459a11 100644
--- a/gui/slick/interfaces/default/config_providers.tmpl
+++ b/gui/slick/interfaces/default/config_providers.tmpl
@@ -61,7 +61,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                   #end if                  
                 </ul>
                 
-                <div id="core-component-group1" class="component-group">
+                <div id="core-component-group1" class="component-group" style='min-height: 550px;'>
 
                     <div class="component-group-desc">
                         <h3>Provider Priorities</h3>
@@ -75,9 +75,9 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                         #end if
 
                         <div>
-                            <h4 class="note">*</h4><p class="note">Provider does not support backlog searches at this time.</p>
-                            <h4 class="note">**</h4><p class="note">Provider supports <b>limited</b> backlog searches, all episodes/qualities may not be available.</p>
-                            <h4 class="note">!</h4><p class="note">Provider is <b>NOT WORKING</b>.</p>
+                            <p class="note">* Provider does not support backlog searches at this time.</p>
+                            <p class="note">** Provider supports <b>limited</b> backlog searches, all episodes/qualities may not be available.</p>
+                            <p class="note">! Provider is <b>NOT WORKING</b>.</p>
                         </div>
                     </div>
 
diff --git a/gui/slick/interfaces/default/config_search.tmpl b/gui/slick/interfaces/default/config_search.tmpl
index 5956180b5bc5c6d09d4e7178a4c7e5264291d259..8f08fb0693001e4c16f2813bf9fa4666c3adac56 100755
--- a/gui/slick/interfaces/default/config_search.tmpl
+++ b/gui/slick/interfaces/default/config_search.tmpl
@@ -51,6 +51,15 @@
 								</span>
 							</label>
 						</div>
+						<div class="field-pair">
+							<label for="randomize_providers">
+								<span class="component-title">Randomize Providers</span>
+								<span class="component-desc">
+									<input type="checkbox" name="randomize_providers" id="randomize_providers" class="enabler" <%= html_checked if sickbeard.RANDOMIZE_PROVIDERS == True else '' %>/>
+									<p>randomize the provider search order instead of going in order of placement</p>
+								</span>
+							</label>
+						</div>
 						<div id="content_download_propers">
 							<div class="field-pair">
 								<label for="check_propers_interval">
diff --git a/gui/slick/interfaces/default/config_subtitles.tmpl b/gui/slick/interfaces/default/config_subtitles.tmpl
index 420d3e54abe81dc1ad74a8fc6076d49b1f098fdd..90d18bc3834235ebbb84c73daaf4eafea656b813 100644
--- a/gui/slick/interfaces/default/config_subtitles.tmpl
+++ b/gui/slick/interfaces/default/config_subtitles.tmpl
@@ -106,6 +106,13 @@
 		                                <span class="component-desc">Log downloaded Subtitle on History page?</span>
 		                            </label>
 		                        </div>                        		
+		                        <div class="field-pair">
+		                            <input type="checkbox" name="subtitles_multi" id="subtitles_multi" #if $sickbeard.SUBTITLES_MULTI then " checked=\"checked\"" else ""#/>
+		                            <label class="clearfix" for="subtitles_multi">
+		                                <span class="component-title">Subtitles Multi-Language</span>
+		                                <span class="component-desc">Append language codes to subtitle filenames?</span>
+		                            </label>
+		                        </div>
 	                    <br/><input type="submit" class="btn config_submitter" value="Save Changes" /><br/>
                         </div>
                     </fieldset>
diff --git a/gui/slick/interfaces/default/displayShow.tmpl b/gui/slick/interfaces/default/displayShow.tmpl
index e2715fa0dbad5f4bf406b811ec405a65c342d403..f2bfb9a7641b8d1e5477de98f5f539c9c160dc26 100644
--- a/gui/slick/interfaces/default/displayShow.tmpl
+++ b/gui/slick/interfaces/default/displayShow.tmpl
@@ -244,7 +244,7 @@
 					</tr>
 				#end if
 
-				<tr><td class="showLegend">Size:</td><td>$sickbeard.helpers.human(sickbeard.helpers.get_size($showLoc[0]))</td></tr>
+				<tr><td class="showLegend">Size:</td><td>$sickbeard.helpers.pretty_filesize(sickbeard.helpers.get_size($showLoc[0]))</td></tr>
 
 				</table>
 			
diff --git a/gui/slick/interfaces/default/history.tmpl b/gui/slick/interfaces/default/history.tmpl
index dcf7a0fc99fa9e8ca10d9ab6c9b98800505c22bf..33995fd5c0439e12f332c55035c6a0bad4adf502 100644
--- a/gui/slick/interfaces/default/history.tmpl
+++ b/gui/slick/interfaces/default/history.tmpl
@@ -128,7 +128,7 @@
 			<tr>
 				#set $curdatetime = $datetime.datetime.strptime(str($hItem["date"]), $history.dateFormat)
 				<td align="center"><div class="${fuzzydate}">$sbdatetime.sbdatetime.sbfdatetime($curdatetime, show_seconds=True)</div><span class="sort_data">$time.mktime($curdatetime.timetuple())</span></td>
-				<td class="tvShow" width="35%"><a href="$sbRoot/home/displayShow?show=$hItem["showid"]#season-$hItem["season"]">$hItem["show_name"] - <%="S%02i" % int(hItem["season"])+"E%02i" % int(hItem["episode"]) %>#if "proper" in $hItem["resource"].lower or "repack" in $hItem["resource"].lower then ' <span class="quality Proper">Proper</span>' else ""#</a></td>
+				<td class="tvShow" width="35%"><a href="$sbRoot/home/displayShow?show=$hItem["showid"]#season-$hItem["season"]">$hItem["show_name"] - <%="S%02i" % int(hItem["season"])+"E%02i" % int(hItem["episode"]) %>#if "proper" in $hItem["resource"].lower() or "repack" in $hItem["resource"].lower() then ' <span class="quality Proper">Proper</span>' else ""#</a></td>
 				<td align="center" #if $curStatus == SUBTITLED then 'class="subtitles_column"' else ''#>
 				#if $curStatus == SUBTITLED:
 					<img width="16" height="11" style="vertical-align:middle;" src="$sbRoot/images/flags/<%= hItem["resource"][len(hItem["resource"])-6:len(hItem["resource"])-4]+'.png'%>">
@@ -190,7 +190,7 @@
 				#set $curdatetime = $datetime.datetime.strptime(str($hItem["actions"][0]["time"]), $history.dateFormat)
 				<td align="center"><div class="${fuzzydate}">$sbdatetime.sbdatetime.sbfdatetime($curdatetime, show_seconds=True)</div><span class="sort_data">$time.mktime($curdatetime.timetuple())</span></td>
 				<td class="tvShow" width="25%">
-					<span><a href="$sbRoot/home/displayShow?show=$hItem["show_id"]#season-$hItem["season"]">$hItem["show_name"] - <%="S%02i" % int(hItem["season"])+"E%02i" % int(hItem["episode"]) %>#if "proper" in $hItem["resource"].lower or "repack" in $hItem["resource"].lower then ' <span class="quality Proper">Proper</span>' else ""#</a></span>
+					<span><a href="$sbRoot/home/displayShow?show=$hItem["show_id"]#season-$hItem["season"]">$hItem["show_name"] - <%="S%02i" % int(hItem["season"])+"E%02i" % int(hItem["episode"]) %>#if "proper" in $hItem["resource"].lower() or "repack" in $hItem["resource"].lower() then ' <span class="quality Proper">Proper</span>' else ""#</a></span>
 				</td>
 				<td align="center" provider="<%=str(sorted(hItem["actions"])[0]["provider"])%>">
 					#for $action in sorted($hItem["actions"]):
diff --git a/gui/slick/interfaces/default/home.tmpl b/gui/slick/interfaces/default/home.tmpl
index fe1403b4b9bbda2efbca54a8a52fec9df705c974..12cf6a9597c71ac8134bbd0f5720b6455c8b5636 100644
--- a/gui/slick/interfaces/default/home.tmpl
+++ b/gui/slick/interfaces/default/home.tmpl
@@ -214,6 +214,10 @@
 			<option value="$sbRoot/setHomeLayout/?layout=banner" #if $sickbeard.HOME_LAYOUT == "banner" then "selected=\"selected\"" else ""#>Banner</option>
 			<option value="$sbRoot/setHomeLayout/?layout=simple" #if $sickbeard.HOME_LAYOUT == "simple" then "selected=\"selected\"" else ""#>Simple</option>
 		</select>
+		#if $layout != 'poster':
+        Search:
+			<input class="search form-control form-control-inline input-sm input200" type="search" data-column="1" placeholder="Search Show Name"> <button type="button" class="resetshows resetanime btn btn-inline">Reset Search</button>
+		#end if
 	</span>
 	
 	#if $layout == 'poster':
@@ -238,12 +242,6 @@
 	#end if
 </div>
 
-#if $layout != 'poster':
-<div class="pull-right">
-	<input class="search form-control form-control-inline input-sm input200" type="search" data-column="1" placeholder="Search Show Name"> <button type="button" class="resetshows resetanime btn btn-inline">Reset Search</button>
-</div>
-#end if
-
 #for $curShowlist in $showlists:
 #set $curListType = $curShowlist[0]
 #set $myShowList = $list($curShowlist[1])
@@ -603,7 +601,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
 		</td>
 	
         <td align="center">
-			<img src="$sbRoot/images/#if int($curShow.paused) == 0 and $curShow.status != "Ended" then "yes16.png\" alt=\"Y\"" else "no16.png\" alt=\"N\""# width="16" height="16" />
+			<img src="$sbRoot/images/#if int($curShow.paused) == 0 and $curShow.status == "Continuing" then "yes16.png\" alt=\"Y\"" else "no16.png\" alt=\"N\""# width="16" height="16" />
 		</td>
 		
         <td align="center">
diff --git a/gui/slick/interfaces/default/home_newShow.tmpl b/gui/slick/interfaces/default/home_newShow.tmpl
index 0e9f7e57768f50f78fb2ca8a4ea38fe34d85e667..de6f13450927a9ed4a6e6a8e139ffe05921922df 100644
--- a/gui/slick/interfaces/default/home_newShow.tmpl
+++ b/gui/slick/interfaces/default/home_newShow.tmpl
@@ -44,11 +44,11 @@
 			<input type="hidden" id="indexer_timeout" value="$sickbeard.INDEXER_TIMEOUT" />
 
 			#if $use_provided_info:
-				Show retrieved from existing metadata: <a href="<%= anon_url(sickbeard.indexerApi(provided_indexer).config['show_url'], provided_indexer_id) %>">$provided_indexer_name</a>
-				<input type="hidden" name="indexerLang" value="en" />
-				<input type="hidden" name="whichSeries" value="$provided_indexer_id" />
+				Show retrieved from existing metadata: <a href="$anon_url($sickbeard.indexerApi($provided_indexer).config['show_url'], $provided_indexer_id)">$provided_indexer_name</a>
+				<input type="hidden" id="indexerLang" name="indexerLang" value="en" />
+				<input type="hidden" id="whichSeries" name="whichSeries" value="$provided_indexer_id" />
+				<input type="hidden" id="providedIndexer" name="providedIndexer" value="$provided_indexer" />
 				<input type="hidden" id="providedName" value="$provided_indexer_name" />
-				<input type="hidden" id="providedIndexer" value="$provided_indexer" />
 			#else:
 				
 				<input type="text" id="nameToSearch" value="$default_show_name" class="form-control form-control-inline input-sm input350" />
diff --git a/gui/slick/interfaces/default/home_trendingShows.tmpl b/gui/slick/interfaces/default/home_trendingShows.tmpl
index ab0c02a97d966849f6420a8ec938713ad4c543e9..cd483fa43c5cc57c6f3ad924c25cb3b814397413 100644
--- a/gui/slick/interfaces/default/home_trendingShows.tmpl
+++ b/gui/slick/interfaces/default/home_trendingShows.tmpl
@@ -14,6 +14,7 @@
 #import os.path
 #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
 
+<script type="text/javascript" src="$sbRoot/js/addTrendingShow.js?$sbPID"></script>
 <script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
 <script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
 
@@ -90,7 +91,6 @@
 	<h1 class="title">$title</h1>
 #end if
 
-#if $trending_shows
 <div id="tabs">
     <ul>
         <li><a href="#tabs-1">Manage Directories</a></li>
@@ -119,46 +119,10 @@
 		<option value="desc">Desc</option>
 	</select>
 </div>
-#end if
-
-<div id="container">
-
-#if None is $trending_shows
-	<div class="trakt_show" style="width:100%; margin-top:20px">
-		<p class="red-text">Trakt API did not return results, this can happen from time to time.
-		<br /><br />This view should auto refresh every 10 mins.</p>
-	</div>
-#else
-#for $cur_show in $trending_shows:
-#if not 'ExistsInLibrary' in $cur_show['tvdb_id']:
-
-#set $image = re.sub(r'(?im)(.*)(\..*?)$', r'\1-300\2', $cur_show['images']['poster'], 0)
-
-	<div class="trakt_show" data-name="$cur_show['title']" data-rating="$cur_show['ratings']['percentage']" data-votes="$cur_show['ratings']['votes']">
-		<div class="traktContainer">
-			<div class="trakt-image">
-				<a class="trakt-image" href="<%= anon_url(cur_show['url']) %>" target="_blank"><img alt="" class="trakt-image" src="${image}" /></a>
-			</div>
 
-			<div class="show-title">
-				<%= (cur_show['title'], '<span>&nbsp;</span>')[ '' == cur_show['title']] %>
-			</div>
-
-		<div class="clearfix">
-			<p>$cur_show['ratings']['percentage']% <img src="$sbRoot/images/heart.png"></p>
-			<i>$cur_show['ratings']['votes'] votes</i>
-
-			<div class="traktShowTitleIcons">
-				<a href="$sbRoot/home/addTraktShow?indexer_id=${cur_show['tvdb_id']}&amp;showName=${cur_show['title']}" class="btn btn-xs">Add Show</a>
-			</div>
-		</div>
-		</div>
-	</div>
-
-#end if
-#end for
-#end if
-</div>
+<br />
+<div id="trendingShows"></div>
+<br />
 
 <script type="text/javascript" charset="utf-8">
 <!--
diff --git a/gui/slick/interfaces/default/inc_bottom.tmpl b/gui/slick/interfaces/default/inc_bottom.tmpl
index f6621060777aa4bb075ee9c5d293ecc6e7540df5..5c9dfd5730a199c7301dd63e2acccbea13651c26 100644
--- a/gui/slick/interfaces/default/inc_bottom.tmpl
+++ b/gui/slick/interfaces/default/inc_bottom.tmpl
@@ -27,7 +27,7 @@
 		#set $sql_result = $myDB.select($sql_statement)
 
 		#set $shows_total = len($sickbeard.showList)
-		#set $shows_active = len([show for show in $sickbeard.showList if show.paused == 0 and show.status != "Ended"])
+		#set $shows_active = len([show for show in $sickbeard.showList if show.paused == 0 and show.status == "Continuing"])
 
 		#if $sql_result:
 			#set $ep_snatched = $sql_result[0]['ep_snatched']
diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl
index f59736896d1626588c18fedbe7c6f6f215e4a8ae..93a9057b35b5e30eed7309cc00c497997db21a94 100644
--- a/gui/slick/interfaces/default/inc_top.tmpl
+++ b/gui/slick/interfaces/default/inc_top.tmpl
@@ -109,8 +109,8 @@
 				\$("#SubMenu a:contains('Manage Torrents')").addClass('btn').html('<span class="submenu-icon-bittorrent pull-left"></span> Manage Torrents');
 				\$("#SubMenu a[href$='/manage/failedDownloads/']").addClass('btn').html('<span class="submenu-icon-failed-download pull-left"></span> Failed Downloads');
 				\$("#SubMenu a:contains('Notification')").addClass('btn').html('<span class="ui-icon ui-icon-note pull-left"></span> Notifications');
-				\$("#SubMenu a:contains('Update show in XBMC')").addClass('btn').html('<span class="submenu-icon-xbmc pull-left"></span> Update show in XBMC');
-				\$("#SubMenu a[href$='/home/updateXBMC/']").addClass('btn').html('<span class="submenu-icon-xbmc pull-left"></span> Update XBMC');
+				\$("#SubMenu a:contains('Update show in KODI')").addClass('btn').html('<span class="submenu-icon-kodi pull-left"></span> Update show in KODI');
+				\$("#SubMenu a[href$='/home/updateKODI/']").addClass('btn').html('<span class="submenu-icon-kodi pull-left"></span> Update KODI');
 			}
 		
 			\$(document).ready(function() {
@@ -140,6 +140,7 @@
 					<a class="navbar-brand" href="$sbRoot/home/" title="SickRage"><img alt="SickRage" src="$sbRoot/images/sickrage.png" style="height: 50px;" class="img-responsive pull-left" /></a>
 				</div>
 
+			#if $sbLogin:
 				<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
 					<ul class="nav navbar-nav navbar-right">
 						<li id="NAVhome" class="dropdown">
@@ -169,8 +170,8 @@
                             #if $sickbeard.USE_PLEX and $sickbeard.PLEX_SERVER_HOST != "":
 								<li><a href="$sbRoot/home/updatePLEX/"><i class="menu-icon-backlog-view"></i>&nbsp;Update PLEX</a></li>
 							#end if
-							#if $sickbeard.USE_XBMC and $sickbeard.XBMC_HOST != "":
-								<li><a href="$sbRoot/home/updateXBMC/"><i class="menu-icon-xbmc"></i>&nbsp;Update XBMC</a></li>
+							#if $sickbeard.USE_KODI and $sickbeard.KODI_HOST != "":
+								<li><a href="$sbRoot/home/updateKODI/"><i class="menu-icon-kodi"></i>&nbsp;Update KODI</a></li>
 							#end if	
 							#if $sickbeard.USE_TORRENTS and $sickbeard.TORRENT_METHOD != 'blackhole' \
 							and ($sickbeard.ENABLE_HTTPS and $sickbeard.TORRENT_HOST[:5] == 'https' \
@@ -212,14 +213,15 @@
 						<li class="dropdown">
 							<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="$sbRoot/images/menu/system18-2.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">System <b class="caret"></b></span></a>
 							<ul class="dropdown-menu">
-								<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck"><i class="menu-icon-update"></i>&nbsp;Force Version Check</a></li>
+								<li><a href="$sbRoot/home/updateCheck?pid=$sbPID"><i class="menu-icon-update"></i>&nbsp;Check For Updates</a></li>
                                 <li><a href="$sbRoot/home/restart/?pid=$sbPID" class="confirm restart"><i class="menu-icon-restart"></i>&nbsp;Restart</a></li>
                                 <li><a href="$sbRoot/home/shutdown/?pid=$sbPID" class="confirm shutdown"><i class="menu-icon-shutdown"></i>&nbsp;Shutdown</a></li>
+                                <li><a href="$sbRoot/logout" class="confirm logout"><i class="menu-icon-shutdown"></i>&nbsp;Logout</a></li>
 							</ul>
 						</li>
 			            <li id="donate"><a href="http://sr-upgrade.appspot.com" rel="noreferrer" onclick="window.open('${sickbeard.ANON_REDIRECT}' + this.href); return false;"><img src="$sbRoot/images/donate.jpg" alt="[donate]" class="navbaricon hidden-xs" /></a></li>
 					</ul>
-
+			#end if
 				</div><!-- /.navbar-collapse -->
 			</div><!-- /.container-fluid -->
 		</nav>
diff --git a/gui/slick/interfaces/default/login.tmpl b/gui/slick/interfaces/default/login.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..4bf403944234c073cb42d57a14b77c600b51aa35
--- /dev/null
+++ b/gui/slick/interfaces/default/login.tmpl
@@ -0,0 +1,21 @@
+#import sickbeard
+
+#set global $title="Login"
+
+#set global $sbPath = ".."
+
+#set global $topmenu="login"#
+#import os.path
+#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
+
+<div class="login">
+    <form action="" method="post">
+        <h1>SickRage</h1>
+        <div class="ctrlHolder"><input class="inlay" name="username" type="text" placeholder="Username" autocomplete="off" /></div>
+        <div class="ctrlHolder"><input class="inlay" name="password" type="password" placeholder="Password" autocomplete="off" /></div>
+        <div class="ctrlHolder">
+            <label class="remember_me" title="for 30 days"><input class="inlay" id="remember_me" name="remember_me" type="checkbox" value="1" checked="checked" /> Remember me</label>
+            <input class="button" name="submit" type="submit" value="Login" />
+        </div>
+    </form>
+</div>
\ No newline at end of file
diff --git a/gui/slick/interfaces/default/manage_manageSearches.tmpl b/gui/slick/interfaces/default/manage_manageSearches.tmpl
index a3de903374ee6afeec13de2f590afa58052aa391..f8d0e8b375997b8a72edc192e88d76f14a4fc43c 100644
--- a/gui/slick/interfaces/default/manage_manageSearches.tmpl
+++ b/gui/slick/interfaces/default/manage_manageSearches.tmpl
@@ -47,10 +47,6 @@ In Progress<br />
 #end if
 <br />
 
-<h3>Version Check:</h3>
-<a class="btn" href="$sbRoot/manage/manageSearches/forceVersionCheck"><i class="icon-check"></i> Force Check</a>
-<br /> <br />
-
 <h3>Search Queue:</h3>
 Backlog: <i>$queueLength['backlog'] pending items</i></br>
 Daily: <i>$queueLength['daily'] pending items</i></br>
diff --git a/gui/slick/interfaces/default/trendingShows.tmpl b/gui/slick/interfaces/default/trendingShows.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..df15b040c8fdacd6a0466278535b82cfc0446e35
--- /dev/null
+++ b/gui/slick/interfaces/default/trendingShows.tmpl
@@ -0,0 +1,102 @@
+#import sickbeard
+#import datetime
+#import re
+#import os.path
+#from sickbeard.common import *
+#from sickbeard import sbdatetime
+#from sickbeard.helpers import anon_url
+
+<script type="text/javascript" charset="utf-8">
+<!--
+
+\$(document).ready(function(){
+    // initialise combos for dirty page refreshes
+    \$('#showsort').val('original');
+    \$('#showsortdirection').val('asc');
+
+    var \$container = [\$('#container')];
+    jQuery.each(\$container, function (j) {
+        this.isotope({
+            itemSelector: '.trakt_show',
+            sortBy: 'original-order',
+            layoutMode: 'fitRows',
+            getSortData: {
+                name: function( itemElem ) {
+                    var name = \$( itemElem ).attr('data-name') || '';
+#if not $sickbeard.SORT_ARTICLE:
+                    name = name.replace(/^(The|A|An)\s/i, '');
+#end if
+                    return name.toLowerCase();
+                },
+                rating: '[data-rating] parseInt',
+                votes: '[data-votes] parseInt',
+            }
+        });
+    });
+
+    \$('#showsort').on( 'change', function() {
+        var sortCriteria;
+        switch (this.value) {
+            case 'original':
+                sortCriteria = 'original-order'
+                break;
+            case 'rating':
+                /* randomise, else the rating_votes can already
+                 * have sorted leaving this with nothing to do.
+                 */
+                \$('#container').isotope({sortBy: 'random'});
+                sortCriteria = 'rating';
+                break;
+            case 'rating_votes':
+                sortCriteria = ['rating', 'votes'];
+                break;
+            case 'votes':
+                sortCriteria = 'votes';
+                break;
+            default:
+                sortCriteria = 'name'
+                break;
+        }
+        \$('#container').isotope({sortBy: sortCriteria});
+    });
+
+    \$('#showsortdirection').on( 'change', function() {
+        \$('#container').isotope({sortAscending: ('asc' == this.value)});
+    });
+});
+
+//-->
+</script>
+
+<div id="container">
+#if not $trending_shows
+	<div class="trakt_show" style="width:100%; margin-top:20px">
+		<p class="red-text">Trakt API did not return any results, please check your config.
+	</div>
+#else
+#for $cur_show in $trending_shows:
+    #set $image = re.sub(r'(?im)(.*)(\..*?)$', r'\1-300\2', $cur_show['images']['poster'], 0)
+
+    <div class="trakt_show" data-name="$cur_show['title']" data-rating="$cur_show['ratings']['percentage']" data-votes="$cur_show['ratings']['votes']">
+        <div class="traktContainer">
+            <div class="trakt-image">
+                <a class="trakt-image" href="<%= anon_url(cur_show['url']) %>" target="_blank"><img alt="" class="trakt-image" src="${image}" /></a>
+            </div>
+
+            <div class="show-title">
+                <%= (cur_show['title'], '<span>&nbsp;</span>')[ '' == cur_show['title']] %>
+            </div>
+
+        <div class="clearfix">
+            <p>$cur_show['ratings']['percentage']% <img src="$sbRoot/images/heart.png"></p>
+            <i>$cur_show['ratings']['votes'] votes</i>
+
+            <div class="traktShowTitleIcons">
+                <a href="$sbRoot/home/addShows/addTraktShow?indexer_id=${cur_show['tvdb_id'] or cur_show['tvrage_id']}&amp;showName=${cur_show['title']}" class="btn btn-xs">Add Show</a>
+            </div>
+        </div>
+        </div>
+    </div>
+#end for
+#end if
+</div>
\ No newline at end of file
diff --git a/gui/slick/js/addExistingShow.js b/gui/slick/js/addExistingShow.js
index fa27e10ad489b23892a6cfcffe6c42b38b04c717..cdf17b1cda5f9b9616d6a3005e7bb9b41836d0eb 100644
--- a/gui/slick/js/addExistingShow.js
+++ b/gui/slick/js/addExistingShow.js
@@ -1,49 +1,50 @@
-$(document).ready(function() { 
+$(document).ready(function() {
 
-    $('#checkAll').live('click', function(){
-    
-        var seasCheck = this;
+    $('#tableDiv').on('click', '#checkAll', function() {
 
-        $('.dirCheck').each(function(){
-           this.checked = seasCheck.checked;
+        var seasCheck = this;
+        $('.dirCheck').each(function() {
+            this.checked = seasCheck.checked;
         });
+
     });
 
-    $('#submitShowDirs').click(function(){
+    $('#submitShowDirs').click(function() {
 
         var dirArr = new Array();
-
         $('.dirCheck').each(function(i,w) {
-        if (this.checked == true) {
-            var show = $(this).attr('id');
-            var indexer = $(this).closest('tr').find('select').val();
-            dirArr.push(encodeURIComponent(indexer + '|' + show));
-        }
-        });  
+            if (this.checked == true) {
+                var show = $(this).attr('id');
+                var indexer = $(this).closest('tr').find('select').val();
+                dirArr.push(encodeURIComponent(indexer + '|' + show));
+            }
+        });
 
-        if (dirArr.length == 0)
+        if (dirArr.length == 0) {
             return false;
+        }
 
-        url = sbRoot+'/home/addShows/addExistingShows?promptForSettings='+ ($('#promptForSettings').prop('checked') ? 'on' : 'off');
-
-        url += '&shows_to_add='+dirArr.join('&shows_to_add=');
+        url = sbRoot + '/home/addShows/addExistingShows?promptForSettings=' + ($('#promptForSettings').prop('checked') ? 'on' : 'off');
+        url += '&shows_to_add=' + dirArr.join('&shows_to_add=');
 
         window.location.href = url;
+
     });
 
 
     function loadContent() {
         var url = '';
-        $('.dir_check').each(function(i,w){
+        $('.dir_check').each(function(i,w) {
             if ($(w).is(':checked')) {
-                if (url.length)
+                if (url.length) {
                     url += '&';
+                }
                 url += 'rootDir=' + encodeURIComponent($(w).attr('id'));
             }
         });
 
-        $('#tableDiv').html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> loading folders...');
-        $.get(sbRoot+'/home/addShows/massAddTable', url, function(data) {
+        $('#tableDiv').html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32.gif" height="32" width="32" /> loading folders...');
+        $.get(sbRoot + '/home/addShows/massAddTable/', url, function(data) {
             $('#tableDiv').html(data);
             $("#addRootDirTable").tablesorter({
                 //sortList: [[1,0]],
@@ -58,21 +59,24 @@ $(document).ready(function() {
 
     var last_txt = '';
     $('#rootDirText').change(function() {
-        if (last_txt == $('#rootDirText').val())
+        if (last_txt == $('#rootDirText').val()) {
             return false;
-        else
+        } else {
             last_txt = $('#rootDirText').val();
-        $('#rootDirStaticList').html('');           
+        }
+        $('#rootDirStaticList').html('');
         $('#rootDirs option').each(function(i, w) {
-            $('#rootDirStaticList').append('<li class="ui-state-default ui-corner-all"><input type="checkbox" class="dir_check" id="'+$(w).val()+'" checked=checked> <label for="'+$(w).val()+'" style="color:#09A2FF;"><b>'+$(w).val()+'</b></label></li>')
+            $('#rootDirStaticList').append('<li class="ui-state-default ui-corner-all"><input type="checkbox" class="cb dir_check" id="' + $(w).val() + '" checked=checked> <label for="' + $(w).val() + '"><b>' + $(w).val() + '</b></label></li>');
         });
         loadContent();
     });
-    
-    $('.dir_check').live('click', loadContent);
 
-    $('.showManage').live('click', function() {
-      $( "#tabs" ).tabs( 'select', 0 );
+    $('#rootDirStaticList').on('click', '.dir_check', loadContent);
+
+    $('#tableDiv').on('click', '.showManage', function(event) {
+        event.preventDefault();
+        $("#tabs").tabs('option', 'active', 0);
+        $('html,body').animate({scrollTop:0}, 1000);
     });
 
 });
\ No newline at end of file
diff --git a/gui/slick/js/addTrendingShow.js b/gui/slick/js/addTrendingShow.js
new file mode 100644
index 0000000000000000000000000000000000000000..137acce016a8ee9a2b5738c393db47a31698e32d
--- /dev/null
+++ b/gui/slick/js/addTrendingShow.js
@@ -0,0 +1,21 @@
+$(document).ready(function() {
+    var trendingRequestXhr = null;
+
+    function loadContent() {
+        if (trendingRequestXhr) trendingRequestXhr.abort();
+
+        $('#trendingShows').html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> loading trending shows...');
+        trendingRequestXhr = $.ajax({
+            url: sbRoot + '/home/addShows/getTrendingShows/',
+            timeout: 60 * 1000,
+            error: function () {
+                $('#trendingShows').empty().html('Trakt timed out, refresh page to try again');
+            },
+            success: function (data) {
+                $('#trendingShows').html(data);
+            }
+        });
+    }
+
+    loadContent();
+});
diff --git a/gui/slick/js/ajaxEpSearch.js b/gui/slick/js/ajaxEpSearch.js
index acb84eae8ecfb323756244d4ce007d96f1c3f2ca..d45026733e86e090e4fbdcaa60edd81d4bcdcbda 100644
--- a/gui/slick/js/ajaxEpSearch.js
+++ b/gui/slick/js/ajaxEpSearch.js
@@ -1,4 +1,4 @@
-var search_status_url = sbRoot + '/getManualSearchStatus';
+var search_status_url = sbRoot + '/home/getManualSearchStatus';
 PNotify.prototype.options.maxonscreen = 5;
 
 $.fn.manualSearches = [];
diff --git a/gui/slick/js/apibuilder.js b/gui/slick/js/apibuilder.js
index 8f7afdc3dcff875338515ab13f5c1bb4197ad8a7..48dafdca45d6be44befae965fe1d75ff7f8bec78 100644
--- a/gui/slick/js/apibuilder.js
+++ b/gui/slick/js/apibuilder.js
@@ -11,16 +11,16 @@ var _disable_empty_list=false;
 var _hide_empty_list=false;
 
 function goListGroup(apikey, L7, L6, L5, L4, L3, L2, L1){
-    var GlobalOptions = "";
+    var html, GlobalOptions = "";
     $('.global').each(function(){
         var checked = $(this).prop('checked');
         if(checked) {
             var globalID = $(this).attr('id');
             // handle jsonp/callback global option differently
             if(globalID == "jsonp") {
-            	GlobalOptions = GlobalOptions + "&" + globalID + "=foo";
+                GlobalOptions = GlobalOptions + "&" + globalID + "=foo";
             } else {
-            	GlobalOptions = GlobalOptions + "&" + globalID + "=1";
+                GlobalOptions = GlobalOptions + "&" + globalID + "=1";
             }
         }
     });
@@ -28,7 +28,7 @@ function goListGroup(apikey, L7, L6, L5, L4, L3, L2, L1){
     // handle the show.getposter / show.getbanner differently as they return an image and not json
     if (L1 == "?cmd=show.getposter" || L1 == "?cmd=show.getbanner") {
         var imgcache = sbRoot + "/api/" + apikey + "/" + L1 + L2 + GlobalOptions;
-        var html = imgcache + '<br/><br/><img src="' + sbRoot + '/images/loading16.gif" id="imgcache">';
+        html = imgcache + '<br/><br/><img src="' + sbRoot + '/images/loading16.gif" id="imgcache">';
         $('#apiResponse').html(html);
         $.ajax({
           url: sbRoot + "/api/" + apikey + "/" + L1 + L2 + GlobalOptions,
@@ -38,14 +38,14 @@ function goListGroup(apikey, L7, L6, L5, L4, L3, L2, L1){
           success: function (img) {
             $('#imgcache').attr('src', imgcache);
           }
-        })
+        });
     }
     else {
-        var html = sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + GlobalOptions + "<br/><pre>";
+        html = sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + GlobalOptions + "<br/><pre>";
         html += $.ajax({
           url: sbRoot + "/api/" + apikey + "/" + L1 + L2 + L3 + L4 + L5 + L6 + L7 + GlobalOptions,
           async: false,
-          dataType: "html",
+          dataType: "html"
         }).responseText;
 
         html += '</pre>';
@@ -189,13 +189,13 @@ function cs_getCookie(name) {
 
 function cs_optionOBJ(type,text,value,label,css) { this.type=type; this.text=text; this.value=value; this.label=label; this.css=css; }
 function cs_getOptions(menu,list) {
-  var opt=new Array();
+  var opt=[];
   for (var i=0; i<menu.items.length; i++) {
     opt[i]=new cs_optionOBJ(menu.items[i].type, menu.items[i].dis, menu.items[i].link, menu.items[i].label, menu.items[i].css);
   }
   if (opt.length==0 && menu.name!="") {
     cs_getSubList(menu.name,list);
-    opt[0]=new cs_optionOBJ(cs_L, "loading ...", "", "", "");
+    //opt[0]=new cs_optionOBJ(cs_L, "loading ...", "", "", "");
   }
   return opt;
 }
@@ -638,4 +638,4 @@ function selectOptions(n,opts,mode) {
     }
   }  
 }
-// ------
+// ------
\ No newline at end of file
diff --git a/gui/slick/js/config.js b/gui/slick/js/config.js
index 813095e9e8e39cf9aaa6d8f797ea864eb9a8c51a..5ca1ae1f1752d0c8eae833a1ff14608cbef3713d 100644
--- a/gui/slick/js/config.js
+++ b/gui/slick/js/config.js
@@ -58,7 +58,7 @@ $(document).ready(function(){
 
     $('#api_key').click(function(){ $('#api_key').select() });
     $("#generate_new_apikey").click(function(){
-        $.get(sbRoot + '/config/general/generateKey', 
+        $.get(sbRoot + '/config/general/generateApiKey',
             function(data){
                 if (data.error != undefined) {
                     alert(data.error);
diff --git a/gui/slick/js/configBackupRestore.js b/gui/slick/js/configBackupRestore.js
index 113f5586949014bee669e81bd724174f516399d5..af00757da5f6ae8869883bd64ad2e9353dab0dd2 100644
--- a/gui/slick/js/configBackupRestore.js
+++ b/gui/slick/js/configBackupRestore.js
@@ -5,7 +5,7 @@ $(document).ready(function(){
         $("#Backup").attr("disabled", true);
         $('#Backup-result').html(loading);
         var backupDir = $("#backupDir").val();
-        $.get(sbRoot + "/config/backup", {'backupDir': backupDir})
+        $.get(sbRoot + "/config/backuprestore/backup", {'backupDir': backupDir})
             .done(function (data) {
                 $('#Backup-result').html(data);
                 $("#Backup").attr("disabled", false);
@@ -15,7 +15,7 @@ $(document).ready(function(){
         $("#Restore").attr("disabled", true);
         $('#Restore-result').html(loading);
         var backupFile = $("#backupFile").val();
-        $.get(sbRoot + "/config/restore", {'backupFile': backupFile})
+        $.get(sbRoot + "/config/backuprestore/restore", {'backupFile': backupFile})
             .done(function (data) {
                 $('#Restore-result').html(data);
                 $("#Restore").attr("disabled", false);
diff --git a/gui/slick/js/configNotifications.js b/gui/slick/js/configNotifications.js
index d430aa960504113a6638aef5ed597d0e0106e0ea..1f1c8f2af118c1b3b4b0f23abbb205eee32ccbe0 100644
--- a/gui/slick/js/configNotifications.js
+++ b/gui/slick/js/configNotifications.js
@@ -37,22 +37,22 @@ $(document).ready(function(){
             });
     });
 	
-    $('#testXBMC').click(function () {
-        var xbmc_host = $.trim($('#xbmc_host').val());
-        var xbmc_username = $.trim($('#xbmc_username').val());
-        var xbmc_password = $.trim($('#xbmc_password').val());
-        if (!xbmc_host) {
-            $('#testXBMC-result').html('Please fill out the necessary fields above.');
-			$('#xbmc_host').addClass('warning');
+    $('#testKODI').click(function () {
+        var kodi_host = $.trim($('#kodi_host').val());
+        var kodi_username = $.trim($('#kodi_username').val());
+        var kodi_password = $.trim($('#kodi_password').val());
+        if (!kodi_host) {
+            $('#testKODI-result').html('Please fill out the necessary fields above.');
+			$('#kodi_host').addClass('warning');
             return;
         }
-        $('#xbmc_host').removeClass('warning');
+        $('#kodi_host').removeClass('warning');
 		$(this).prop('disabled', true);
-        $('#testXBMC-result').html(loading);
-        $.get(sbRoot + '/home/testXBMC', {'host': xbmc_host, 'username': xbmc_username, 'password': xbmc_password})
+        $('#testKODI-result').html(loading);
+        $.get(sbRoot + '/home/testKODI', {'host': kodi_host, 'username': kodi_username, 'password': kodi_password})
             .done(function (data) {
-                $('#testXBMC-result').html(data);
-                $('#testXBMC').prop('disabled', false);
+                $('#testKODI-result').html(data);
+                $('#testKODI').prop('disabled', false);
             });
     });
 
diff --git a/lib/concurrent/__init__.py b/lib/concurrent/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b36383a61027f0875a3cb103edc8f2a4528a3289
--- /dev/null
+++ b/lib/concurrent/__init__.py
@@ -0,0 +1,3 @@
+from pkgutil import extend_path
+
+__path__ = extend_path(__path__, __name__)
diff --git a/lib/concurrent/futures/__init__.py b/lib/concurrent/futures/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fef528199e986ea58fc1ff051500736d78ca8228
--- /dev/null
+++ b/lib/concurrent/futures/__init__.py
@@ -0,0 +1,23 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Execute computations asynchronously using threads or processes."""
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+from concurrent.futures._base import (FIRST_COMPLETED,
+                                      FIRST_EXCEPTION,
+                                      ALL_COMPLETED,
+                                      CancelledError,
+                                      TimeoutError,
+                                      Future,
+                                      Executor,
+                                      wait,
+                                      as_completed)
+from concurrent.futures.thread import ThreadPoolExecutor
+
+# Jython doesn't have multiprocessing
+try:
+    from concurrent.futures.process import ProcessPoolExecutor
+except ImportError:
+    pass
diff --git a/lib/concurrent/futures/_base.py b/lib/concurrent/futures/_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f0c0f3b91deaef93165e640dc75ba7b02677213
--- /dev/null
+++ b/lib/concurrent/futures/_base.py
@@ -0,0 +1,605 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+from __future__ import with_statement
+import logging
+import threading
+import time
+
+from concurrent.futures._compat import reraise
+
+try:
+    from collections import namedtuple
+except ImportError:
+    from concurrent.futures._compat import namedtuple
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+FIRST_COMPLETED = 'FIRST_COMPLETED'
+FIRST_EXCEPTION = 'FIRST_EXCEPTION'
+ALL_COMPLETED = 'ALL_COMPLETED'
+_AS_COMPLETED = '_AS_COMPLETED'
+
+# Possible future states (for internal use by the futures package).
+PENDING = 'PENDING'
+RUNNING = 'RUNNING'
+# The future was cancelled by the user...
+CANCELLED = 'CANCELLED'
+# ...and _Waiter.add_cancelled() was called by a worker.
+CANCELLED_AND_NOTIFIED = 'CANCELLED_AND_NOTIFIED'
+FINISHED = 'FINISHED'
+
+_FUTURE_STATES = [
+    PENDING,
+    RUNNING,
+    CANCELLED,
+    CANCELLED_AND_NOTIFIED,
+    FINISHED
+]
+
+_STATE_TO_DESCRIPTION_MAP = {
+    PENDING: "pending",
+    RUNNING: "running",
+    CANCELLED: "cancelled",
+    CANCELLED_AND_NOTIFIED: "cancelled",
+    FINISHED: "finished"
+}
+
+# Logger for internal use by the futures package.
+LOGGER = logging.getLogger("concurrent.futures")
+
+class Error(Exception):
+    """Base class for all future-related exceptions."""
+    pass
+
+class CancelledError(Error):
+    """The Future was cancelled."""
+    pass
+
+class TimeoutError(Error):
+    """The operation exceeded the given deadline."""
+    pass
+
+class _Waiter(object):
+    """Provides the event that wait() and as_completed() block on."""
+    def __init__(self):
+        self.event = threading.Event()
+        self.finished_futures = []
+
+    def add_result(self, future):
+        self.finished_futures.append(future)
+
+    def add_exception(self, future):
+        self.finished_futures.append(future)
+
+    def add_cancelled(self, future):
+        self.finished_futures.append(future)
+
+class _AsCompletedWaiter(_Waiter):
+    """Used by as_completed()."""
+
+    def __init__(self):
+        super(_AsCompletedWaiter, self).__init__()
+        self.lock = threading.Lock()
+
+    def add_result(self, future):
+        with self.lock:
+            super(_AsCompletedWaiter, self).add_result(future)
+            self.event.set()
+
+    def add_exception(self, future):
+        with self.lock:
+            super(_AsCompletedWaiter, self).add_exception(future)
+            self.event.set()
+
+    def add_cancelled(self, future):
+        with self.lock:
+            super(_AsCompletedWaiter, self).add_cancelled(future)
+            self.event.set()
+
+class _FirstCompletedWaiter(_Waiter):
+    """Used by wait(return_when=FIRST_COMPLETED)."""
+
+    def add_result(self, future):
+        super(_FirstCompletedWaiter, self).add_result(future)
+        self.event.set()
+
+    def add_exception(self, future):
+        super(_FirstCompletedWaiter, self).add_exception(future)
+        self.event.set()
+
+    def add_cancelled(self, future):
+        super(_FirstCompletedWaiter, self).add_cancelled(future)
+        self.event.set()
+
+class _AllCompletedWaiter(_Waiter):
+    """Used by wait(return_when=FIRST_EXCEPTION and ALL_COMPLETED)."""
+
+    def __init__(self, num_pending_calls, stop_on_exception):
+        self.num_pending_calls = num_pending_calls
+        self.stop_on_exception = stop_on_exception
+        self.lock = threading.Lock()
+        super(_AllCompletedWaiter, self).__init__()
+
+    def _decrement_pending_calls(self):
+        with self.lock:
+            self.num_pending_calls -= 1
+            if not self.num_pending_calls:
+                self.event.set()
+
+    def add_result(self, future):
+        super(_AllCompletedWaiter, self).add_result(future)
+        self._decrement_pending_calls()
+
+    def add_exception(self, future):
+        super(_AllCompletedWaiter, self).add_exception(future)
+        if self.stop_on_exception:
+            self.event.set()
+        else:
+            self._decrement_pending_calls()
+
+    def add_cancelled(self, future):
+        super(_AllCompletedWaiter, self).add_cancelled(future)
+        self._decrement_pending_calls()
+
+class _AcquireFutures(object):
+    """A context manager that does an ordered acquire of Future conditions."""
+
+    def __init__(self, futures):
+        self.futures = sorted(futures, key=id)
+
+    def __enter__(self):
+        for future in self.futures:
+            future._condition.acquire()
+
+    def __exit__(self, *args):
+        for future in self.futures:
+            future._condition.release()
+
+def _create_and_install_waiters(fs, return_when):
+    if return_when == _AS_COMPLETED:
+        waiter = _AsCompletedWaiter()
+    elif return_when == FIRST_COMPLETED:
+        waiter = _FirstCompletedWaiter()
+    else:
+        pending_count = sum(
+                f._state not in [CANCELLED_AND_NOTIFIED, FINISHED] for f in fs)
+
+        if return_when == FIRST_EXCEPTION:
+            waiter = _AllCompletedWaiter(pending_count, stop_on_exception=True)
+        elif return_when == ALL_COMPLETED:
+            waiter = _AllCompletedWaiter(pending_count, stop_on_exception=False)
+        else:
+            raise ValueError("Invalid return condition: %r" % return_when)
+
+    for f in fs:
+        f._waiters.append(waiter)
+
+    return waiter
+
+def as_completed(fs, timeout=None):
+    """An iterator over the given futures that yields each as it completes.
+
+    Args:
+        fs: The sequence of Futures (possibly created by different Executors) to
+            iterate over.
+        timeout: The maximum number of seconds to wait. If None, then there
+            is no limit on the wait time.
+
+    Returns:
+        An iterator that yields the given Futures as they complete (finished or
+        cancelled).
+
+    Raises:
+        TimeoutError: If the entire result iterator could not be generated
+            before the given timeout.
+    """
+    if timeout is not None:
+        end_time = timeout + time.time()
+
+    with _AcquireFutures(fs):
+        finished = set(
+                f for f in fs
+                if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
+        pending = set(fs) - finished
+        waiter = _create_and_install_waiters(fs, _AS_COMPLETED)
+
+    try:
+        for future in finished:
+            yield future
+
+        while pending:
+            if timeout is None:
+                wait_timeout = None
+            else:
+                wait_timeout = end_time - time.time()
+                if wait_timeout < 0:
+                    raise TimeoutError(
+                            '%d (of %d) futures unfinished' % (
+                            len(pending), len(fs)))
+
+            waiter.event.wait(wait_timeout)
+
+            with waiter.lock:
+                finished = waiter.finished_futures
+                waiter.finished_futures = []
+                waiter.event.clear()
+
+            for future in finished:
+                yield future
+                pending.remove(future)
+
+    finally:
+        for f in fs:
+            f._waiters.remove(waiter)
+
+DoneAndNotDoneFutures = namedtuple(
+        'DoneAndNotDoneFutures', 'done not_done')
+def wait(fs, timeout=None, return_when=ALL_COMPLETED):
+    """Wait for the futures in the given sequence to complete.
+
+    Args:
+        fs: The sequence of Futures (possibly created by different Executors) to
+            wait upon.
+        timeout: The maximum number of seconds to wait. If None, then there
+            is no limit on the wait time.
+        return_when: Indicates when this function should return. The options
+            are:
+
+            FIRST_COMPLETED - Return when any future finishes or is
+                              cancelled.
+            FIRST_EXCEPTION - Return when any future finishes by raising an
+                              exception. If no future raises an exception
+                              then it is equivalent to ALL_COMPLETED.
+            ALL_COMPLETED -   Return when all futures finish or are cancelled.
+
+    Returns:
+        A named 2-tuple of sets. The first set, named 'done', contains the
+        futures that completed (is finished or cancelled) before the wait
+        completed. The second set, named 'not_done', contains uncompleted
+        futures.
+    """
+    with _AcquireFutures(fs):
+        done = set(f for f in fs
+                   if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
+        not_done = set(fs) - done
+
+        if (return_when == FIRST_COMPLETED) and done:
+            return DoneAndNotDoneFutures(done, not_done)
+        elif (return_when == FIRST_EXCEPTION) and done:
+            if any(f for f in done
+                   if not f.cancelled() and f.exception() is not None):
+                return DoneAndNotDoneFutures(done, not_done)
+
+        if len(done) == len(fs):
+            return DoneAndNotDoneFutures(done, not_done)
+
+        waiter = _create_and_install_waiters(fs, return_when)
+
+    waiter.event.wait(timeout)
+    for f in fs:
+        f._waiters.remove(waiter)
+
+    done.update(waiter.finished_futures)
+    return DoneAndNotDoneFutures(done, set(fs) - done)
+
+class Future(object):
+    """Represents the result of an asynchronous computation."""
+
+    def __init__(self):
+        """Initializes the future. Should not be called by clients."""
+        self._condition = threading.Condition()
+        self._state = PENDING
+        self._result = None
+        self._exception = None
+        self._traceback = None
+        self._waiters = []
+        self._done_callbacks = []
+
+    def _invoke_callbacks(self):
+        for callback in self._done_callbacks:
+            try:
+                callback(self)
+            except Exception:
+                LOGGER.exception('exception calling callback for %r', self)
+
+    def __repr__(self):
+        with self._condition:
+            if self._state == FINISHED:
+                if self._exception:
+                    return '<Future at %s state=%s raised %s>' % (
+                        hex(id(self)),
+                        _STATE_TO_DESCRIPTION_MAP[self._state],
+                        self._exception.__class__.__name__)
+                else:
+                    return '<Future at %s state=%s returned %s>' % (
+                        hex(id(self)),
+                        _STATE_TO_DESCRIPTION_MAP[self._state],
+                        self._result.__class__.__name__)
+            return '<Future at %s state=%s>' % (
+                    hex(id(self)),
+                   _STATE_TO_DESCRIPTION_MAP[self._state])
+
+    def cancel(self):
+        """Cancel the future if possible.
+
+        Returns True if the future was cancelled, False otherwise. A future
+        cannot be cancelled if it is running or has already completed.
+        """
+        with self._condition:
+            if self._state in [RUNNING, FINISHED]:
+                return False
+
+            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+                return True
+
+            self._state = CANCELLED
+            self._condition.notify_all()
+
+        self._invoke_callbacks()
+        return True
+
+    def cancelled(self):
+        """Return True if the future has cancelled."""
+        with self._condition:
+            return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]
+
+    def running(self):
+        """Return True if the future is currently executing."""
+        with self._condition:
+            return self._state == RUNNING
+
+    def done(self):
+        """Return True of the future was cancelled or finished executing."""
+        with self._condition:
+            return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]
+
+    def __get_result(self):
+        if self._exception:
+            reraise(self._exception, self._traceback)
+        else:
+            return self._result
+
+    def add_done_callback(self, fn):
+        """Attaches a callable that will be called when the future finishes.
+
+        Args:
+            fn: A callable that will be called with this future as its only
+                argument when the future completes or is cancelled. The callable
+                will always be called by a thread in the same process in which
+                it was added. If the future has already completed or been
+                cancelled then the callable will be called immediately. These
+                callables are called in the order that they were added.
+        """
+        with self._condition:
+            if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]:
+                self._done_callbacks.append(fn)
+                return
+        fn(self)
+
+    def result(self, timeout=None):
+        """Return the result of the call that the future represents.
+
+        Args:
+            timeout: The number of seconds to wait for the result if the future
+                isn't done. If None, then there is no limit on the wait time.
+
+        Returns:
+            The result of the call that the future represents.
+
+        Raises:
+            CancelledError: If the future was cancelled.
+            TimeoutError: If the future didn't finish executing before the given
+                timeout.
+            Exception: If the call raised then that exception will be raised.
+        """
+        with self._condition:
+            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+                raise CancelledError()
+            elif self._state == FINISHED:
+                return self.__get_result()
+
+            self._condition.wait(timeout)
+
+            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+                raise CancelledError()
+            elif self._state == FINISHED:
+                return self.__get_result()
+            else:
+                raise TimeoutError()
+
+    def exception_info(self, timeout=None):
+        """Return a tuple of (exception, traceback) raised by the call that the
+        future represents.
+
+        Args:
+            timeout: The number of seconds to wait for the exception if the
+                future isn't done. If None, then there is no limit on the wait
+                time.
+
+        Returns:
+            The exception raised by the call that the future represents or None
+            if the call completed without raising.
+
+        Raises:
+            CancelledError: If the future was cancelled.
+            TimeoutError: If the future didn't finish executing before the given
+                timeout.
+        """
+        with self._condition:
+            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+                raise CancelledError()
+            elif self._state == FINISHED:
+                return self._exception, self._traceback
+
+            self._condition.wait(timeout)
+
+            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+                raise CancelledError()
+            elif self._state == FINISHED:
+                return self._exception, self._traceback
+            else:
+                raise TimeoutError()
+
+    def exception(self, timeout=None):
+        """Return the exception raised by the call that the future represents.
+
+        Args:
+            timeout: The number of seconds to wait for the exception if the
+                future isn't done. If None, then there is no limit on the wait
+                time.
+
+        Returns:
+            The exception raised by the call that the future represents or None
+            if the call completed without raising.
+
+        Raises:
+            CancelledError: If the future was cancelled.
+            TimeoutError: If the future didn't finish executing before the given
+                timeout.
+        """
+        return self.exception_info(timeout)[0]
+
+    # The following methods should only be used by Executors and in tests.
+    def set_running_or_notify_cancel(self):
+        """Mark the future as running or process any cancel notifications.
+
+        Should only be used by Executor implementations and unit tests.
+
+        If the future has been cancelled (cancel() was called and returned
+        True) then any threads waiting on the future completing (though calls
+        to as_completed() or wait()) are notified and False is returned.
+
+        If the future was not cancelled then it is put in the running state
+        (future calls to running() will return True) and True is returned.
+
+        This method should be called by Executor implementations before
+        executing the work associated with this future. If this method returns
+        False then the work should not be executed.
+
+        Returns:
+            False if the Future was cancelled, True otherwise.
+
+        Raises:
+            RuntimeError: if this method was already called or if set_result()
+                or set_exception() was called.
+        """
+        with self._condition:
+            if self._state == CANCELLED:
+                self._state = CANCELLED_AND_NOTIFIED
+                for waiter in self._waiters:
+                    waiter.add_cancelled(self)
+                # self._condition.notify_all() is not necessary because
+                # self.cancel() triggers a notification.
+                return False
+            elif self._state == PENDING:
+                self._state = RUNNING
+                return True
+            else:
+                LOGGER.critical('Future %s in unexpected state: %s',
+                                id(self.future),
+                                self.future._state)
+                raise RuntimeError('Future in unexpected state')
+
+    def set_result(self, result):
+        """Sets the return value of work associated with the future.
+
+        Should only be used by Executor implementations and unit tests.
+        """
+        with self._condition:
+            self._result = result
+            self._state = FINISHED
+            for waiter in self._waiters:
+                waiter.add_result(self)
+            self._condition.notify_all()
+        self._invoke_callbacks()
+
+    def set_exception_info(self, exception, traceback):
+        """Sets the result of the future as being the given exception
+        and traceback.
+
+        Should only be used by Executor implementations and unit tests.
+        """
+        with self._condition:
+            self._exception = exception
+            self._traceback = traceback
+            self._state = FINISHED
+            for waiter in self._waiters:
+                waiter.add_exception(self)
+            self._condition.notify_all()
+        self._invoke_callbacks()
+
+    def set_exception(self, exception):
+        """Sets the result of the future as being the given exception.
+
+        Should only be used by Executor implementations and unit tests.
+        """
+        self.set_exception_info(exception, None)
+
+class Executor(object):
+    """This is an abstract base class for concrete asynchronous executors."""
+
+    def submit(self, fn, *args, **kwargs):
+        """Submits a callable to be executed with the given arguments.
+
+        Schedules the callable to be executed as fn(*args, **kwargs) and returns
+        a Future instance representing the execution of the callable.
+
+        Returns:
+            A Future representing the given call.
+        """
+        raise NotImplementedError()
+
+    def map(self, fn, *iterables, **kwargs):
+        """Returns a iterator equivalent to map(fn, iter).
+
+        Args:
+            fn: A callable that will take as many arguments as there are
+                passed iterables.
+            timeout: The maximum number of seconds to wait. If None, then there
+                is no limit on the wait time.
+
+        Returns:
+            An iterator equivalent to: map(func, *iterables) but the calls may
+            be evaluated out-of-order.
+
+        Raises:
+            TimeoutError: If the entire result iterator could not be generated
+                before the given timeout.
+            Exception: If fn(*args) raises for any values.
+        """
+        timeout = kwargs.get('timeout')
+        if timeout is not None:
+            end_time = timeout + time.time()
+
+        fs = [self.submit(fn, *args) for args in zip(*iterables)]
+
+        try:
+            for future in fs:
+                if timeout is None:
+                    yield future.result()
+                else:
+                    yield future.result(end_time - time.time())
+        finally:
+            for future in fs:
+                future.cancel()
+
+    def shutdown(self, wait=True):
+        """Clean-up the resources associated with the Executor.
+
+        It is safe to call this method several times. Otherwise, no other
+        methods can be called after this one.
+
+        Args:
+            wait: If True then shutdown will not return until all running
+                futures have finished executing and the resources used by the
+                executor have been reclaimed.
+        """
+        pass
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.shutdown(wait=True)
+        return False
diff --git a/lib/concurrent/futures/_compat.py b/lib/concurrent/futures/_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..e77cf0e546fcf665fe9e8c9723c01e294564c2e5
--- /dev/null
+++ b/lib/concurrent/futures/_compat.py
@@ -0,0 +1,111 @@
+from keyword import iskeyword as _iskeyword
+from operator import itemgetter as _itemgetter
+import sys as _sys
+
+
+def namedtuple(typename, field_names):
+    """Returns a new subclass of tuple with named fields.
+
+    >>> Point = namedtuple('Point', 'x y')
+    >>> Point.__doc__                   # docstring for the new class
+    'Point(x, y)'
+    >>> p = Point(11, y=22)             # instantiate with positional args or keywords
+    >>> p[0] + p[1]                     # indexable like a plain tuple
+    33
+    >>> x, y = p                        # unpack like a regular tuple
+    >>> x, y
+    (11, 22)
+    >>> p.x + p.y                       # fields also accessable by name
+    33
+    >>> d = p._asdict()                 # convert to a dictionary
+    >>> d['x']
+    11
+    >>> Point(**d)                      # convert from a dictionary
+    Point(x=11, y=22)
+    >>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
+    Point(x=100, y=22)
+
+    """
+
+    # Parse and validate the field names.  Validation serves two purposes,
+    # generating informative error messages and preventing template injection attacks.
+    if isinstance(field_names, basestring):
+        field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
+    field_names = tuple(map(str, field_names))
+    for name in (typename,) + field_names:
+        if not all(c.isalnum() or c=='_' for c in name):
+            raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
+        if _iskeyword(name):
+            raise ValueError('Type names and field names cannot be a keyword: %r' % name)
+        if name[0].isdigit():
+            raise ValueError('Type names and field names cannot start with a number: %r' % name)
+    seen_names = set()
+    for name in field_names:
+        if name.startswith('_'):
+            raise ValueError('Field names cannot start with an underscore: %r' % name)
+        if name in seen_names:
+            raise ValueError('Encountered duplicate field name: %r' % name)
+        seen_names.add(name)
+
+    # Create and fill-in the class template
+    numfields = len(field_names)
+    argtxt = repr(field_names).replace("'", "")[1:-1]   # tuple repr without parens or quotes
+    reprtxt = ', '.join('%s=%%r' % name for name in field_names)
+    dicttxt = ', '.join('%r: t[%d]' % (name, pos) for pos, name in enumerate(field_names))
+    template = '''class %(typename)s(tuple):
+        '%(typename)s(%(argtxt)s)' \n
+        __slots__ = () \n
+        _fields = %(field_names)r \n
+        def __new__(_cls, %(argtxt)s):
+            return _tuple.__new__(_cls, (%(argtxt)s)) \n
+        @classmethod
+        def _make(cls, iterable, new=tuple.__new__, len=len):
+            'Make a new %(typename)s object from a sequence or iterable'
+            result = new(cls, iterable)
+            if len(result) != %(numfields)d:
+                raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
+            return result \n
+        def __repr__(self):
+            return '%(typename)s(%(reprtxt)s)' %% self \n
+        def _asdict(t):
+            'Return a new dict which maps field names to their values'
+            return {%(dicttxt)s} \n
+        def _replace(_self, **kwds):
+            'Return a new %(typename)s object replacing specified fields with new values'
+            result = _self._make(map(kwds.pop, %(field_names)r, _self))
+            if kwds:
+                raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
+            return result \n
+        def __getnewargs__(self):
+            return tuple(self) \n\n''' % locals()
+    for i, name in enumerate(field_names):
+        template += '        %s = _property(_itemgetter(%d))\n' % (name, i)
+
+    # Execute the template string in a temporary namespace and
+    # support tracing utilities by setting a value for frame.f_globals['__name__']
+    namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
+                     _property=property, _tuple=tuple)
+    try:
+        exec(template, namespace)
+    except SyntaxError:
+        e = _sys.exc_info()[1]
+        raise SyntaxError(e.message + ':\n' + template)
+    result = namespace[typename]
+
+    # For pickling to work, the __module__ variable needs to be set to the frame
+    # where the named tuple is created.  Bypass this step in enviroments where
+    # sys._getframe is not defined (Jython for example).
+    if hasattr(_sys, '_getframe'):
+        result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
+
+    return result
+
+
+if _sys.version_info[0] < 3:
+    def reraise(exc, traceback):
+        locals_ = {'exc_type': type(exc), 'exc_value': exc, 'traceback': traceback}
+        exec('raise exc_type, exc_value, traceback', {}, locals_)
+else:
+    def reraise(exc, traceback):
+        # Tracebacks are embedded in exceptions in Python 3
+        raise exc
diff --git a/lib/concurrent/futures/process.py b/lib/concurrent/futures/process.py
new file mode 100644
index 0000000000000000000000000000000000000000..98684f8e8e4ad3fae0d6e7aa89980bf84e0597df
--- /dev/null
+++ b/lib/concurrent/futures/process.py
@@ -0,0 +1,363 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Implements ProcessPoolExecutor.
+
+The follow diagram and text describe the data-flow through the system:
+
+|======================= In-process =====================|== Out-of-process ==|
+
++----------+     +----------+       +--------+     +-----------+    +---------+
+|          |  => | Work Ids |    => |        |  => | Call Q    | => |         |
+|          |     +----------+       |        |     +-----------+    |         |
+|          |     | ...      |       |        |     | ...       |    |         |
+|          |     | 6        |       |        |     | 5, call() |    |         |
+|          |     | 7        |       |        |     | ...       |    |         |
+| Process  |     | ...      |       | Local  |     +-----------+    | Process |
+|  Pool    |     +----------+       | Worker |                      |  #1..n  |
+| Executor |                        | Thread |                      |         |
+|          |     +----------- +     |        |     +-----------+    |         |
+|          | <=> | Work Items | <=> |        | <=  | Result Q  | <= |         |
+|          |     +------------+     |        |     +-----------+    |         |
+|          |     | 6: call()  |     |        |     | ...       |    |         |
+|          |     |    future  |     |        |     | 4, result |    |         |
+|          |     | ...        |     |        |     | 3, except |    |         |
++----------+     +------------+     +--------+     +-----------+    +---------+
+
+Executor.submit() called:
+- creates a uniquely numbered _WorkItem and adds it to the "Work Items" dict
+- adds the id of the _WorkItem to the "Work Ids" queue
+
+Local worker thread:
+- reads work ids from the "Work Ids" queue and looks up the corresponding
+  WorkItem from the "Work Items" dict: if the work item has been cancelled then
+  it is simply removed from the dict, otherwise it is repackaged as a
+  _CallItem and put in the "Call Q". New _CallItems are put in the "Call Q"
+  until "Call Q" is full. NOTE: the size of the "Call Q" is kept small because
+  calls placed in the "Call Q" can no longer be cancelled with Future.cancel().
+- reads _ResultItems from "Result Q", updates the future stored in the
+  "Work Items" dict and deletes the dict entry
+
+Process #1..n:
+- reads _CallItems from "Call Q", executes the calls, and puts the resulting
+  _ResultItems in "Request Q"
+"""
+
+from __future__ import with_statement
+import atexit
+import multiprocessing
+import threading
+import weakref
+import sys
+
+from concurrent.futures import _base
+
+try:
+    import queue
+except ImportError:
+    import Queue as queue
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+# Workers are created as daemon threads and processes. This is done to allow the
+# interpreter to exit when there are still idle processes in a
+# ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However,
+# allowing workers to die with the interpreter has two undesirable properties:
+#   - The workers would still be running during interpretor shutdown,
+#     meaning that they would fail in unpredictable ways.
+#   - The workers could be killed while evaluating a work item, which could
+#     be bad if the callable being evaluated has external side-effects e.g.
+#     writing to a file.
+#
+# To work around this problem, an exit handler is installed which tells the
+# workers to exit when their work queues are empty and then waits until the
+# threads/processes finish.
+
+_threads_queues = weakref.WeakKeyDictionary()
+_shutdown = False
+
+def _python_exit():
+    global _shutdown
+    _shutdown = True
+    items = list(_threads_queues.items())
+    for t, q in items:
+        q.put(None)
+    for t, q in items:
+        t.join()
+
+# Controls how many more calls than processes will be queued in the call queue.
+# A smaller number will mean that processes spend more time idle waiting for
+# work while a larger number will make Future.cancel() succeed less frequently
+# (Futures in the call queue cannot be cancelled).
+EXTRA_QUEUED_CALLS = 1
+
+class _WorkItem(object):
+    def __init__(self, future, fn, args, kwargs):
+        self.future = future
+        self.fn = fn
+        self.args = args
+        self.kwargs = kwargs
+
+class _ResultItem(object):
+    def __init__(self, work_id, exception=None, result=None):
+        self.work_id = work_id
+        self.exception = exception
+        self.result = result
+
+class _CallItem(object):
+    def __init__(self, work_id, fn, args, kwargs):
+        self.work_id = work_id
+        self.fn = fn
+        self.args = args
+        self.kwargs = kwargs
+
+def _process_worker(call_queue, result_queue):
+    """Evaluates calls from call_queue and places the results in result_queue.
+
+    This worker is run in a separate process.
+
+    Args:
+        call_queue: A multiprocessing.Queue of _CallItems that will be read and
+            evaluated by the worker.
+        result_queue: A multiprocessing.Queue of _ResultItems that will written
+            to by the worker.
+        shutdown: A multiprocessing.Event that will be set as a signal to the
+            worker that it should exit when call_queue is empty.
+    """
+    while True:
+        call_item = call_queue.get(block=True)
+        if call_item is None:
+            # Wake up queue management thread
+            result_queue.put(None)
+            return
+        try:
+            r = call_item.fn(*call_item.args, **call_item.kwargs)
+        except BaseException:
+            e = sys.exc_info()[1]
+            result_queue.put(_ResultItem(call_item.work_id,
+                                         exception=e))
+        else:
+            result_queue.put(_ResultItem(call_item.work_id,
+                                         result=r))
+
+def _add_call_item_to_queue(pending_work_items,
+                            work_ids,
+                            call_queue):
+    """Fills call_queue with _WorkItems from pending_work_items.
+
+    This function never blocks.
+
+    Args:
+        pending_work_items: A dict mapping work ids to _WorkItems e.g.
+            {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
+        work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids
+            are consumed and the corresponding _WorkItems from
+            pending_work_items are transformed into _CallItems and put in
+            call_queue.
+        call_queue: A multiprocessing.Queue that will be filled with _CallItems
+            derived from _WorkItems.
+    """
+    while True:
+        if call_queue.full():
+            return
+        try:
+            work_id = work_ids.get(block=False)
+        except queue.Empty:
+            return
+        else:
+            work_item = pending_work_items[work_id]
+
+            if work_item.future.set_running_or_notify_cancel():
+                call_queue.put(_CallItem(work_id,
+                                         work_item.fn,
+                                         work_item.args,
+                                         work_item.kwargs),
+                               block=True)
+            else:
+                del pending_work_items[work_id]
+                continue
+
+def _queue_management_worker(executor_reference,
+                             processes,
+                             pending_work_items,
+                             work_ids_queue,
+                             call_queue,
+                             result_queue):
+    """Manages the communication between this process and the worker processes.
+
+    This function is run in a local thread.
+
+    Args:
+        executor_reference: A weakref.ref to the ProcessPoolExecutor that owns
+            this thread. Used to determine if the ProcessPoolExecutor has been
+            garbage collected and that this function can exit.
+        process: A list of the multiprocessing.Process instances used as
+            workers.
+        pending_work_items: A dict mapping work ids to _WorkItems e.g.
+            {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
+        work_ids_queue: A queue.Queue of work ids e.g. Queue([5, 6, ...]).
+        call_queue: A multiprocessing.Queue that will be filled with _CallItems
+            derived from _WorkItems for processing by the process workers.
+        result_queue: A multiprocessing.Queue of _ResultItems generated by the
+            process workers.
+    """
+    nb_shutdown_processes = [0]
+    def shutdown_one_process():
+        """Tell a worker to terminate, which will in turn wake us again"""
+        call_queue.put(None)
+        nb_shutdown_processes[0] += 1
+    while True:
+        _add_call_item_to_queue(pending_work_items,
+                                work_ids_queue,
+                                call_queue)
+
+        result_item = result_queue.get(block=True)
+        if result_item is not None:
+            work_item = pending_work_items[result_item.work_id]
+            del pending_work_items[result_item.work_id]
+
+            if result_item.exception:
+                work_item.future.set_exception(result_item.exception)
+            else:
+                work_item.future.set_result(result_item.result)
+        # Check whether we should start shutting down.
+        executor = executor_reference()
+        # No more work items can be added if:
+        #   - The interpreter is shutting down OR
+        #   - The executor that owns this worker has been collected OR
+        #   - The executor that owns this worker has been shutdown.
+        if _shutdown or executor is None or executor._shutdown_thread:
+            # Since no new work items can be added, it is safe to shutdown
+            # this thread if there are no pending work items.
+            if not pending_work_items:
+                while nb_shutdown_processes[0] < len(processes):
+                    shutdown_one_process()
+                # If .join() is not called on the created processes then
+                # some multiprocessing.Queue methods may deadlock on Mac OS
+                # X.
+                for p in processes:
+                    p.join()
+                call_queue.close()
+                return
+        del executor
+
+_system_limits_checked = False
+_system_limited = None
+def _check_system_limits():
+    global _system_limits_checked, _system_limited
+    if _system_limits_checked:
+        if _system_limited:
+            raise NotImplementedError(_system_limited)
+    _system_limits_checked = True
+    try:
+        import os
+        nsems_max = os.sysconf("SC_SEM_NSEMS_MAX")
+    except (AttributeError, ValueError):
+        # sysconf not available or setting not available
+        return
+    if nsems_max == -1:
+        # indetermine limit, assume that limit is determined
+        # by available memory only
+        return
+    if nsems_max >= 256:
+        # minimum number of semaphores available
+        # according to POSIX
+        return
+    _system_limited = "system provides too few semaphores (%d available, 256 necessary)" % nsems_max
+    raise NotImplementedError(_system_limited)
+
+class ProcessPoolExecutor(_base.Executor):
+    def __init__(self, max_workers=None):
+        """Initializes a new ProcessPoolExecutor instance.
+
+        Args:
+            max_workers: The maximum number of processes that can be used to
+                execute the given calls. If None or not given then as many
+                worker processes will be created as the machine has processors.
+        """
+        _check_system_limits()
+
+        if max_workers is None:
+            self._max_workers = multiprocessing.cpu_count()
+        else:
+            self._max_workers = max_workers
+
+        # Make the call queue slightly larger than the number of processes to
+        # prevent the worker processes from idling. But don't make it too big
+        # because futures in the call queue cannot be cancelled.
+        self._call_queue = multiprocessing.Queue(self._max_workers +
+                                                 EXTRA_QUEUED_CALLS)
+        self._result_queue = multiprocessing.Queue()
+        self._work_ids = queue.Queue()
+        self._queue_management_thread = None
+        self._processes = set()
+
+        # Shutdown is a two-step process.
+        self._shutdown_thread = False
+        self._shutdown_lock = threading.Lock()
+        self._queue_count = 0
+        self._pending_work_items = {}
+
+    def _start_queue_management_thread(self):
+        # When the executor gets lost, the weakref callback will wake up
+        # the queue management thread.
+        def weakref_cb(_, q=self._result_queue):
+            q.put(None)
+        if self._queue_management_thread is None:
+            self._queue_management_thread = threading.Thread(
+                    target=_queue_management_worker,
+                    args=(weakref.ref(self, weakref_cb),
+                          self._processes,
+                          self._pending_work_items,
+                          self._work_ids,
+                          self._call_queue,
+                          self._result_queue))
+            self._queue_management_thread.daemon = True
+            self._queue_management_thread.start()
+            _threads_queues[self._queue_management_thread] = self._result_queue
+
+    def _adjust_process_count(self):
+        for _ in range(len(self._processes), self._max_workers):
+            p = multiprocessing.Process(
+                    target=_process_worker,
+                    args=(self._call_queue,
+                          self._result_queue))
+            p.start()
+            self._processes.add(p)
+
+    def submit(self, fn, *args, **kwargs):
+        with self._shutdown_lock:
+            if self._shutdown_thread:
+                raise RuntimeError('cannot schedule new futures after shutdown')
+
+            f = _base.Future()
+            w = _WorkItem(f, fn, args, kwargs)
+
+            self._pending_work_items[self._queue_count] = w
+            self._work_ids.put(self._queue_count)
+            self._queue_count += 1
+            # Wake up queue management thread
+            self._result_queue.put(None)
+
+            self._start_queue_management_thread()
+            self._adjust_process_count()
+            return f
+    submit.__doc__ = _base.Executor.submit.__doc__
+
+    def shutdown(self, wait=True):
+        with self._shutdown_lock:
+            self._shutdown_thread = True
+        if self._queue_management_thread:
+            # Wake up queue management thread
+            self._result_queue.put(None)
+            if wait:
+                self._queue_management_thread.join()
+        # To reduce the risk of openning too many files, remove references to
+        # objects that use file descriptors.
+        self._queue_management_thread = None
+        self._call_queue = None
+        self._result_queue = None
+        self._processes = None
+    shutdown.__doc__ = _base.Executor.shutdown.__doc__
+
+atexit.register(_python_exit)
diff --git a/lib/concurrent/futures/thread.py b/lib/concurrent/futures/thread.py
new file mode 100644
index 0000000000000000000000000000000000000000..930d16735fe6312295998c7b570fe7df09a52a1c
--- /dev/null
+++ b/lib/concurrent/futures/thread.py
@@ -0,0 +1,138 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Implements ThreadPoolExecutor."""
+
+from __future__ import with_statement
+import atexit
+import threading
+import weakref
+import sys
+
+from concurrent.futures import _base
+
+try:
+    import queue
+except ImportError:
+    import Queue as queue
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+# Workers are created as daemon threads. This is done to allow the interpreter
+# to exit when there are still idle threads in a ThreadPoolExecutor's thread
+# pool (i.e. shutdown() was not called). However, allowing workers to die with
+# the interpreter has two undesirable properties:
+#   - The workers would still be running during interpretor shutdown,
+#     meaning that they would fail in unpredictable ways.
+#   - The workers could be killed while evaluating a work item, which could
+#     be bad if the callable being evaluated has external side-effects e.g.
+#     writing to a file.
+#
+# To work around this problem, an exit handler is installed which tells the
+# workers to exit when their work queues are empty and then waits until the
+# threads finish.
+
+_threads_queues = weakref.WeakKeyDictionary()
+_shutdown = False
+
+def _python_exit():
+    global _shutdown
+    _shutdown = True
+    items = list(_threads_queues.items())
+    for t, q in items:
+        q.put(None)
+    for t, q in items:
+        t.join()
+
+atexit.register(_python_exit)
+
+class _WorkItem(object):
+    def __init__(self, future, fn, args, kwargs):
+        self.future = future
+        self.fn = fn
+        self.args = args
+        self.kwargs = kwargs
+
+    def run(self):
+        if not self.future.set_running_or_notify_cancel():
+            return
+
+        try:
+            result = self.fn(*self.args, **self.kwargs)
+        except BaseException:
+            e, tb = sys.exc_info()[1:]
+            self.future.set_exception_info(e, tb)
+        else:
+            self.future.set_result(result)
+
+def _worker(executor_reference, work_queue):
+    try:
+        while True:
+            work_item = work_queue.get(block=True)
+            if work_item is not None:
+                work_item.run()
+                continue
+            executor = executor_reference()
+            # Exit if:
+            #   - The interpreter is shutting down OR
+            #   - The executor that owns the worker has been collected OR
+            #   - The executor that owns the worker has been shutdown.
+            if _shutdown or executor is None or executor._shutdown:
+                # Notice other workers
+                work_queue.put(None)
+                return
+            del executor
+    except BaseException:
+        _base.LOGGER.critical('Exception in worker', exc_info=True)
+
+class ThreadPoolExecutor(_base.Executor):
+    def __init__(self, max_workers):
+        """Initializes a new ThreadPoolExecutor instance.
+
+        Args:
+            max_workers: The maximum number of threads that can be used to
+                execute the given calls.
+        """
+        self._max_workers = max_workers
+        self._work_queue = queue.Queue()
+        self._threads = set()
+        self._shutdown = False
+        self._shutdown_lock = threading.Lock()
+
+    def submit(self, fn, *args, **kwargs):
+        with self._shutdown_lock:
+            if self._shutdown:
+                raise RuntimeError('cannot schedule new futures after shutdown')
+
+            f = _base.Future()
+            w = _WorkItem(f, fn, args, kwargs)
+
+            self._work_queue.put(w)
+            self._adjust_thread_count()
+            return f
+    submit.__doc__ = _base.Executor.submit.__doc__
+
+    def _adjust_thread_count(self):
+        # When the executor gets lost, the weakref callback will wake up
+        # the worker threads.
+        def weakref_cb(_, q=self._work_queue):
+            q.put(None)
+        # TODO(bquinlan): Should avoid creating new threads if there are more
+        # idle threads than items in the work queue.
+        if len(self._threads) < self._max_workers:
+            t = threading.Thread(target=_worker,
+                                 args=(weakref.ref(self, weakref_cb),
+                                       self._work_queue))
+            t.daemon = True
+            t.start()
+            self._threads.add(t)
+            _threads_queues[t] = self._work_queue
+
+    def shutdown(self, wait=True):
+        with self._shutdown_lock:
+            self._shutdown = True
+            self._work_queue.put(None)
+        if wait:
+            for t in self._threads:
+                t.join()
+    shutdown.__doc__ = _base.Executor.shutdown.__doc__
diff --git a/lib/futures/__init__.py b/lib/futures/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f8b23481f5185cef0f4296817cb78b86e0d3544
--- /dev/null
+++ b/lib/futures/__init__.py
@@ -0,0 +1,24 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Execute computations asynchronously using threads or processes."""
+
+import warnings
+
+from concurrent.futures import (FIRST_COMPLETED,
+                                FIRST_EXCEPTION,
+                                ALL_COMPLETED,
+                                CancelledError,
+                                TimeoutError,
+                                Future,
+                                Executor,
+                                wait,
+                                as_completed,
+                                ProcessPoolExecutor,
+                                ThreadPoolExecutor)
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+warnings.warn('The futures package has been deprecated. '
+              'Use the concurrent.futures package instead.',
+              DeprecationWarning)
diff --git a/lib/futures/process.py b/lib/futures/process.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9d37b16ce5b00c97e8179174108d641557a6ac5
--- /dev/null
+++ b/lib/futures/process.py
@@ -0,0 +1 @@
+from concurrent.futures import ProcessPoolExecutor
diff --git a/lib/futures/thread.py b/lib/futures/thread.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6bd05de66220c53f594987eec35cf47e69dfd2a
--- /dev/null
+++ b/lib/futures/thread.py
@@ -0,0 +1 @@
+from concurrent.futures import ThreadPoolExecutor
diff --git a/lib/imdb/__init__.py b/lib/imdb/__init__.py
index 0cdc9650f61526e4b6c9be30cc9853e79d7ce8bb..5114dd22db58eb836b7e3d147f6e1a66549ba0dc 100644
--- a/lib/imdb/__init__.py
+++ b/lib/imdb/__init__.py
@@ -25,7 +25,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 __all__ = ['IMDb', 'IMDbError', 'Movie', 'Person', 'Character', 'Company',
             'available_access_systems']
-__version__ = VERSION = '5.0'
+__version__ = VERSION = '5.1dev20141116'
 
 # Import compatibility module (importing it is enough).
 import _compat
diff --git a/lib/imdb/imdbpy.cfg b/lib/imdb/imdbpy.cfg
deleted file mode 100644
index 68b305386518020c110d81e30adaea4bcbbc9f2c..0000000000000000000000000000000000000000
--- a/lib/imdb/imdbpy.cfg
+++ /dev/null
@@ -1,78 +0,0 @@
-#
-# IMDbPY configuration file.
-#
-# This file can be placed in many locations; the first file found is
-# used, _ignoring_ the content of the others.
-#
-# Place it in one of the following directories (in order of precedence):
-#
-# - imdbpy.cfg in the current directory.
-# - .imdbpy.cfg in the current directory.
-# - imdbpy.cfg in the user's home directory.
-# - .imdbpy.cfg in the user's home directory.
-# - /etc/imdbpy.cfg Unix-like systems only.
-# - /etc/conf.d/imdbpy.cfg Unix-like systems only.
-# - sys.prefix + imdbpy.cfg for non-Unix (e.g.: C:\Python\etc\imdbpy.cfg)
-#
-# If this file is not found, 'http' access system is used by default.
-#
-# Lines starting with #, ; and // are considered comments and ignored.
-# 
-# Some special values are replaced with Python equivalents (case insensitive):
-#
-# 0, off, false, no  ->  False
-# 1, on, true, yes   ->  True
-# none               ->  None
-#
-# Other options, like defaultModFunct, must be passed by the code.
-#
-
-[imdbpy]
-## Default.
-accessSystem = httpThin
-
-## Optional (options common to every data access system):
-# Activate adult searches (on, by default).
-#adultSearch = on
-# Number of results for searches (20 by default).
-#results = 20
-# Re-raise all caught exceptions (off, by default).
-#reraiseExceptions = off
-
-## Optional (options common to http and mobile data access systems):
-# Proxy used to access the network.  If it requires authentication,
-# try with: http://username:password@server_address:port/
-#proxy = http://localhost:8080/
-# Cookies of the IMDb.com account
-#cookie_id = string_representing_the_cookie_id
-#cookie_uu = string_representing_the_cookie_uu
-## Timeout for the connection to IMDb (30 seconds, by default).
-#timeout = 30 
-# Base url to access pages on the IMDb.com web server.
-#imdbURL_base = http://akas.imdb.com/
-
-## Parameters for the 'http' data access system.
-# Parser to use; can be a single value or a list of value separated by
-# a comma, to express order preference.  Valid values: "lxml", "beautifulsoup"
-#useModule = lxml,beautifulsoup
-
-## Parameters for the 'mobile' data access system.
-#accessSystem = mobile
-
-## Parameters for the 'sql' data access system.
-#accessSystem = sql
-#uri = mysql://user:password@localhost/imdb
-# ORM to use; can be a single value or a list of value separated by
-# a comma, to express order preference.  Valid values: "sqlobject", "sqlalchemy"
-#useORM = sqlobject,sqlalchemy
-
-## Set the threshold for logging messages.
-# Can be one of "debug", "info", "warning", "error", "critical" (default:
-# "warning").
-loggingLevel = debug
-
-## Path to a configuration file for the logging facility;
-# see: http://docs.python.org/library/logging.html#configuring-logging
-#loggingConfig = ~/.imdbpy-logger.cfg
-
-
diff --git a/lib/imdb/utils.py b/lib/imdb/utils.py
index f468efd4dd7fc0290902b3502301070fea6ae53e..c43cb627a460c06a01e6431d6a01ae275f481d23 100644
--- a/lib/imdb/utils.py
+++ b/lib/imdb/utils.py
@@ -639,11 +639,14 @@ def analyze_company_name(name, stripNotes=False):
     o_name = name
     name = name.strip()
     country = None
-    if name.endswith(']'):
-        idx = name.rfind('[')
-        if idx != -1:
-            country = name[idx:]
-            name = name[:idx].rstrip()
+    if name.startswith('['):
+        name = re.sub('[!@#$\(\)\[\]]', '', name)
+    else:
+        if name.endswith(']'):
+            idx = name.rfind('[')
+            if idx != -1:
+                country = name[idx:]
+                name = name[:idx].rstrip()
     if not name:
         raise IMDbParserError('invalid name: "%s"' % o_name)
     result = {'name': name}
diff --git a/lib/requests/packages/urllib3/contrib/pyopenssl.py b/lib/requests/packages/urllib3/contrib/pyopenssl.py
index d9bda15afea264015291f926a68333a0995ed354..c3df278b1e3e00ee1528b66203e3368a3f71a00a 100644
--- a/lib/requests/packages/urllib3/contrib/pyopenssl.py
+++ b/lib/requests/packages/urllib3/contrib/pyopenssl.py
@@ -59,9 +59,14 @@ HAS_SNI = SUBJ_ALT_NAME_SUPPORT
 # Map from urllib3 to PyOpenSSL compatible parameter-values.
 _openssl_versions = {
     ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
-    ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD,
     ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
 }
+
+try:
+    _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
+except AttributeError:
+    pass
+
 _openssl_verify = {
     ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
     ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
diff --git a/lib/shove/__init__.py b/lib/shove/__init__.py
deleted file mode 100644
index 3be119b4dda15a8302623bfbd226d2dd2b597152..0000000000000000000000000000000000000000
--- a/lib/shove/__init__.py
+++ /dev/null
@@ -1,519 +0,0 @@
-# -*- coding: utf-8 -*-
-'''Common object storage frontend.'''
-
-import os
-import zlib
-import urllib
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
-from collections import deque
-
-try:
-    # Import store and cache entry points if setuptools installed
-    import pkg_resources
-    stores = dict((_store.name, _store) for _store in
-        pkg_resources.iter_entry_points('shove.stores'))
-    caches = dict((_cache.name, _cache) for _cache in
-        pkg_resources.iter_entry_points('shove.caches'))
-    # Pass if nothing loaded
-    if not stores and not caches:
-        raise ImportError()
-except ImportError:
-    # Static store backend registry
-    stores = dict(
-        bsddb='shove.store.bsdb:BsdStore',
-        cassandra='shove.store.cassandra:CassandraStore',
-        dbm='shove.store.dbm:DbmStore',
-        durus='shove.store.durusdb:DurusStore',
-        file='shove.store.file:FileStore',
-        firebird='shove.store.db:DbStore',
-        ftp='shove.store.ftp:FtpStore',
-        hdf5='shove.store.hdf5:HDF5Store',
-        leveldb='shove.store.leveldbstore:LevelDBStore',
-        memory='shove.store.memory:MemoryStore',
-        mssql='shove.store.db:DbStore',
-        mysql='shove.store.db:DbStore',
-        oracle='shove.store.db:DbStore',
-        postgres='shove.store.db:DbStore',
-        redis='shove.store.redisdb:RedisStore',
-        s3='shove.store.s3:S3Store',
-        simple='shove.store.simple:SimpleStore',
-        sqlite='shove.store.db:DbStore',
-        svn='shove.store.svn:SvnStore',
-        zodb='shove.store.zodb:ZodbStore',
-    )
-    # Static cache backend registry
-    caches = dict(
-        bsddb='shove.cache.bsdb:BsdCache',
-        file='shove.cache.file:FileCache',
-        filelru='shove.cache.filelru:FileLRUCache',
-        firebird='shove.cache.db:DbCache',
-        memcache='shove.cache.memcached:MemCached',
-        memlru='shove.cache.memlru:MemoryLRUCache',
-        memory='shove.cache.memory:MemoryCache',
-        mssql='shove.cache.db:DbCache',
-        mysql='shove.cache.db:DbCache',
-        oracle='shove.cache.db:DbCache',
-        postgres='shove.cache.db:DbCache',
-        redis='shove.cache.redisdb:RedisCache',
-        simple='shove.cache.simple:SimpleCache',
-        simplelru='shove.cache.simplelru:SimpleLRUCache',
-        sqlite='shove.cache.db:DbCache',
-    )
-
-
-def getbackend(uri, engines, **kw):
-    '''
-    Loads the right backend based on a URI.
-
-    @param uri Instance or name string
-    @param engines A dictionary of scheme/class pairs
-    '''
-    if isinstance(uri, basestring):
-        mod = engines[uri.split('://', 1)[0]]
-        # Load module if setuptools not present
-        if isinstance(mod, basestring):
-            # Isolate classname from dot path
-            module, klass = mod.split(':')
-            # Load module
-            mod = getattr(__import__(module, '', '', ['']), klass)
-        # Load appropriate class from setuptools entry point
-        else:
-            mod = mod.load()
-        # Return instance
-        return mod(uri, **kw)
-    # No-op for existing instances
-    return uri
-
-
-def synchronized(func):
-    '''
-    Decorator to lock and unlock a method (Phillip J. Eby).
-
-    @param func Method to decorate
-    '''
-    def wrapper(self, *__args, **__kw):
-        self._lock.acquire()
-        try:
-            return func(self, *__args, **__kw)
-        finally:
-            self._lock.release()
-    wrapper.__name__ = func.__name__
-    wrapper.__dict__ = func.__dict__
-    wrapper.__doc__ = func.__doc__
-    return wrapper
-
-
-class Base(object):
-
-    '''Base Mapping class.'''
-
-    def __init__(self, engine, **kw):
-        '''
-        @keyword compress True, False, or an integer compression level (1-9).
-        '''
-        self._compress = kw.get('compress', False)
-        self._protocol = kw.get('protocol', pickle.HIGHEST_PROTOCOL)
-
-    def __getitem__(self, key):
-        raise NotImplementedError()
-
-    def __setitem__(self, key, value):
-        raise NotImplementedError()
-
-    def __delitem__(self, key):
-        raise NotImplementedError()
-
-    def __contains__(self, key):
-        try:
-            value = self[key]
-        except KeyError:
-            return False
-        return True
-
-    def get(self, key, default=None):
-        '''
-        Fetch a given key from the mapping. If the key does not exist,
-        return the default.
-
-        @param key Keyword of item in mapping.
-        @param default Default value (default: None)
-        '''
-        try:
-            return self[key]
-        except KeyError:
-            return default
-
-    def dumps(self, value):
-        '''Optionally serializes and compresses an object.'''
-        # Serialize everything but ASCII strings
-        value = pickle.dumps(value, protocol=self._protocol)
-        if self._compress:
-            level = 9 if self._compress is True else self._compress
-            value = zlib.compress(value, level)
-        return value
-
-    def loads(self, value):
-        '''Deserializes and optionally decompresses an object.'''
-        if self._compress:
-            try:
-                value = zlib.decompress(value)
-            except zlib.error:
-                pass
-        value = pickle.loads(value)
-        return value
-
-
-class BaseStore(Base):
-
-    '''Base Store class (based on UserDict.DictMixin).'''
-
-    def __init__(self, engine, **kw):
-        super(BaseStore, self).__init__(engine, **kw)
-        self._store = None
-
-    def __cmp__(self, other):
-        if other is None:
-            return False
-        if isinstance(other, BaseStore):
-            return cmp(dict(self.iteritems()), dict(other.iteritems()))
-
-    def __del__(self):
-        # __init__ didn't succeed, so don't bother closing
-        if not hasattr(self, '_store'):
-            return
-        self.close()
-
-    def __iter__(self):
-        for k in self.keys():
-            yield k
-
-    def __len__(self):
-        return len(self.keys())
-
-    def __repr__(self):
-        return repr(dict(self.iteritems()))
-
-    def close(self):
-        '''Closes internal store and clears object references.'''
-        try:
-            self._store.close()
-        except AttributeError:
-            pass
-        self._store = None
-
-    def clear(self):
-        '''Removes all keys and values from a store.'''
-        for key in self.keys():
-            del self[key]
-
-    def items(self):
-        '''Returns a list with all key/value pairs in the store.'''
-        return list(self.iteritems())
-
-    def iteritems(self):
-        '''Lazily returns all key/value pairs in a store.'''
-        for k in self:
-            yield (k, self[k])
-
-    def iterkeys(self):
-        '''Lazy returns all keys in a store.'''
-        return self.__iter__()
-
-    def itervalues(self):
-        '''Lazily returns all values in a store.'''
-        for _, v in self.iteritems():
-            yield v
-
-    def keys(self):
-        '''Returns a list with all keys in a store.'''
-        raise NotImplementedError()
-
-    def pop(self, key, *args):
-        '''
-        Removes and returns a value from a store.
-
-        @param args Default to return if key not present.
-        '''
-        if len(args) > 1:
-            raise TypeError('pop expected at most 2 arguments, got ' + repr(
-                1 + len(args))
-            )
-        try:
-            value = self[key]
-        # Return default if key not in store
-        except KeyError:
-            if args:
-                return args[0]
-        del self[key]
-        return value
-
-    def popitem(self):
-        '''Removes and returns a key, value pair from a store.'''
-        try:
-            k, v = self.iteritems().next()
-        except StopIteration:
-            raise KeyError('Store is empty.')
-        del self[k]
-        return (k, v)
-
-    def setdefault(self, key, default=None):
-        '''
-        Returns the value corresponding to an existing key or sets the
-        to key to the default and returns the default.
-
-        @param default Default value (default: None)
-        '''
-        try:
-            return self[key]
-        except KeyError:
-            self[key] = default
-        return default
-
-    def update(self, other=None, **kw):
-        '''
-        Adds to or overwrites the values in this store with values from
-        another store.
-
-        other Another store
-        kw Additional keys and values to store
-        '''
-        if other is None:
-            pass
-        elif hasattr(other, 'iteritems'):
-            for k, v in other.iteritems():
-                self[k] = v
-        elif hasattr(other, 'keys'):
-            for k in other.keys():
-                self[k] = other[k]
-        else:
-            for k, v in other:
-                self[k] = v
-        if kw:
-            self.update(kw)
-
-    def values(self):
-        '''Returns a list with all values in a store.'''
-        return list(v for _, v in self.iteritems())
-
-
-class Shove(BaseStore):
-
-    '''Common object frontend class.'''
-
-    def __init__(self, store='simple://', cache='simple://', **kw):
-        super(Shove, self).__init__(store, **kw)
-        # Load store
-        self._store = getbackend(store, stores, **kw)
-        # Load cache
-        self._cache = getbackend(cache, caches, **kw)
-        # Buffer for lazy writing and setting for syncing frequency
-        self._buffer, self._sync = dict(), kw.get('sync', 2)
-
-    def __getitem__(self, key):
-        '''Gets a item from shove.'''
-        try:
-            return self._cache[key]
-        except KeyError:
-            # Synchronize cache and store
-            self.sync()
-            value = self._store[key]
-            self._cache[key] = value
-            return value
-
-    def __setitem__(self, key, value):
-        '''Sets an item in shove.'''
-        self._cache[key] = self._buffer[key] = value
-        # When the buffer reaches self._limit, writes the buffer to the store
-        if len(self._buffer) >= self._sync:
-            self.sync()
-
-    def __delitem__(self, key):
-        '''Deletes an item from shove.'''
-        try:
-            del self._cache[key]
-        except KeyError:
-            pass
-        self.sync()
-        del self._store[key]
-
-    def keys(self):
-        '''Returns a list of keys in shove.'''
-        self.sync()
-        return self._store.keys()
-
-    def sync(self):
-        '''Writes buffer to store.'''
-        for k, v in self._buffer.iteritems():
-            self._store[k] = v
-        self._buffer.clear()
-
-    def close(self):
-        '''Finalizes and closes shove.'''
-        # If close has been called, pass
-        if self._store is not None:
-            try:
-                self.sync()
-            except AttributeError:
-                pass
-            self._store.close()
-            self._store = self._cache = self._buffer = None
-
-
-class FileBase(Base):
-
-    '''Base class for file based storage.'''
-
-    def __init__(self, engine, **kw):
-        super(FileBase, self).__init__(engine, **kw)
-        if engine.startswith('file://'):
-            engine = urllib.url2pathname(engine.split('://')[1])
-        self._dir = engine
-        # Create directory
-        if not os.path.exists(self._dir):
-            self._createdir()
-
-    def __getitem__(self, key):
-        # (per Larry Meyn)
-        try:
-            item = open(self._key_to_file(key), 'rb')
-            data = item.read()
-            item.close()
-            return self.loads(data)
-        except:
-            raise KeyError(key)
-
-    def __setitem__(self, key, value):
-        # (per Larry Meyn)
-        try:
-            item = open(self._key_to_file(key), 'wb')
-            item.write(self.dumps(value))
-            item.close()
-        except (IOError, OSError):
-            raise KeyError(key)
-
-    def __delitem__(self, key):
-        try:
-            os.remove(self._key_to_file(key))
-        except (IOError, OSError):
-            raise KeyError(key)
-
-    def __contains__(self, key):
-        return os.path.exists(self._key_to_file(key))
-
-    def __len__(self):
-        return len(os.listdir(self._dir))
-
-    def _createdir(self):
-        '''Creates the store directory.'''
-        try:
-            os.makedirs(self._dir)
-        except OSError:
-            raise EnvironmentError(
-                'Cache directory "%s" does not exist and ' \
-                'could not be created' % self._dir
-            )
-
-    def _key_to_file(self, key):
-        '''Gives the filesystem path for a key.'''
-        return os.path.join(self._dir, urllib.quote_plus(key))
-
-    def keys(self):
-        '''Returns a list of keys in the store.'''
-        return [urllib.unquote_plus(name) for name in os.listdir(self._dir)]
-
-
-class SimpleBase(Base):
-
-    '''Single-process in-memory store base class.'''
-
-    def __init__(self, engine, **kw):
-        super(SimpleBase, self).__init__(engine, **kw)
-        self._store = dict()
-
-    def __getitem__(self, key):
-        try:
-            return self._store[key]
-        except:
-            raise KeyError(key)
-
-    def __setitem__(self, key, value):
-        self._store[key] = value
-
-    def __delitem__(self, key):
-        try:
-            del self._store[key]
-        except:
-            raise KeyError(key)
-
-    def __len__(self):
-        return len(self._store)
-
-    def keys(self):
-        '''Returns a list of keys in the store.'''
-        return self._store.keys()
-
-
-class LRUBase(SimpleBase):
-
-    def __init__(self, engine, **kw):
-        super(LRUBase, self).__init__(engine, **kw)
-        self._max_entries = kw.get('max_entries', 300)
-        self._hits = 0
-        self._misses = 0
-        self._queue = deque()
-        self._refcount = dict()
-
-    def __getitem__(self, key):
-        try:
-            value = super(LRUBase, self).__getitem__(key)
-            self._hits += 1
-        except KeyError:
-            self._misses += 1
-            raise
-        self._housekeep(key)
-        return value
-
-    def __setitem__(self, key, value):
-        super(LRUBase, self).__setitem__(key, value)
-        self._housekeep(key)
-        if len(self._store) > self._max_entries:
-            while len(self._store) > self._max_entries:
-                k = self._queue.popleft()
-                self._refcount[k] -= 1
-                if not self._refcount[k]:
-                    super(LRUBase, self).__delitem__(k)
-                    del self._refcount[k]
-
-    def _housekeep(self, key):
-        self._queue.append(key)
-        self._refcount[key] = self._refcount.get(key, 0) + 1
-        if len(self._queue) > self._max_entries * 4:
-            self._purge_queue()
-
-    def _purge_queue(self):
-        for i in [None] * len(self._queue):
-            k = self._queue.popleft()
-            if self._refcount[k] == 1:
-                self._queue.append(k)
-            else:
-                self._refcount[k] -= 1
-
-
-class DbBase(Base):
-
-    '''Database common base class.'''
-
-    def __init__(self, engine, **kw):
-        super(DbBase, self).__init__(engine, **kw)
-
-    def __delitem__(self, key):
-        self._store.delete(self._store.c.key == key).execute()
-
-    def __len__(self):
-        return self._store.count().execute().fetchone()[0]
-
-
-__all__ = ['Shove']
diff --git a/lib/shove/cache/__init__.py b/lib/shove/cache/__init__.py
deleted file mode 100644
index 40a96afc6ff09d58a702b76e3f7dd412fe975e26..0000000000000000000000000000000000000000
--- a/lib/shove/cache/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
diff --git a/lib/shove/cache/db.py b/lib/shove/cache/db.py
deleted file mode 100644
index 21fea01ff79faec878c1a9870cb7e422f6962f4f..0000000000000000000000000000000000000000
--- a/lib/shove/cache/db.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Database object cache.
-
-The shove psuedo-URL used for database object caches is the format used by
-SQLAlchemy:
-
-<driver>://<username>:<password>@<host>:<port>/<database>
-
-<driver> is the database engine. The engines currently supported SQLAlchemy are
-sqlite, mysql, postgres, oracle, mssql, and firebird.
-<username> is the database account user name
-<password> is the database accound password
-<host> is the database location
-<port> is the database port
-<database> is the name of the specific database
-
-For more information on specific databases see:
-
-http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_supported
-'''
-
-import time
-import random
-from datetime import datetime
-try:
-    from sqlalchemy import (
-        MetaData, Table, Column, String, Binary, DateTime, select, update,
-        insert, delete,
-    )
-    from shove import DbBase
-except ImportError:
-    raise ImportError('Requires SQLAlchemy >= 0.4')
-
-__all__ = ['DbCache']
-
-
-class DbCache(DbBase):
-
-    '''database cache backend'''
-
-    def __init__(self, engine, **kw):
-        super(DbCache, self).__init__(engine, **kw)
-        # Get table name
-        tablename = kw.get('tablename', 'cache')
-        # Bind metadata
-        self._metadata = MetaData(engine)
-        # Make cache table
-        self._store = Table(tablename, self._metadata,
-            Column('key', String(60), primary_key=True, nullable=False),
-            Column('value', Binary, nullable=False),
-            Column('expires', DateTime, nullable=False),
-        )
-        # Create cache table if it does not exist
-        if not self._store.exists():
-            self._store.create()
-        # Set maximum entries
-        self._max_entries = kw.get('max_entries', 300)
-        # Maximum number of entries to cull per call if cache is full
-        self._maxcull = kw.get('maxcull', 10)
-        # Set timeout
-        self.timeout = kw.get('timeout', 300)
-
-    def __getitem__(self, key):
-        row = select(
-             [self._store.c.value, self._store.c.expires],
-            self._store.c.key == key
-        ).execute().fetchone()
-        if row is not None:
-            # Remove if item expired
-            if row.expires < datetime.now().replace(microsecond=0):
-                del self[key]
-                raise KeyError(key)
-            return self.loads(str(row.value))
-        raise KeyError(key)
-
-    def __setitem__(self, key, value):
-        timeout, value, cache = self.timeout, self.dumps(value), self._store
-        # Cull if too many items
-        if len(self) >= self._max_entries:
-            self._cull()
-        # Generate expiration time
-        expires = datetime.fromtimestamp(
-            time.time() + timeout
-        ).replace(microsecond=0)
-        # Update database if key already present
-        if key in self:
-            update(
-                cache,
-                cache.c.key == key,
-                dict(value=value, expires=expires),
-            ).execute()
-        # Insert new key if key not present
-        else:
-            insert(
-                cache, dict(key=key, value=value, expires=expires)
-            ).execute()
-
-    def _cull(self):
-        '''Remove items in cache to make more room.'''
-        cache, maxcull = self._store, self._maxcull
-        # Remove items that have timed out
-        now = datetime.now().replace(microsecond=0)
-        delete(cache, cache.c.expires < now).execute()
-        # Remove any items over the maximum allowed number in the cache
-        if len(self) >= self._max_entries:
-            # Upper limit for key query
-            ul = maxcull * 2
-            # Get list of keys
-            keys = [
-                i[0] for i in select(
-                    [cache.c.key], limit=ul
-                ).execute().fetchall()
-            ]
-            # Get some keys at random
-            delkeys = list(random.choice(keys) for i in xrange(maxcull))
-            delete(cache, cache.c.key.in_(delkeys)).execute()
diff --git a/lib/shove/cache/file.py b/lib/shove/cache/file.py
deleted file mode 100644
index 7b9a4ae7b7f75a7489f2bfa16a7a20912ac29993..0000000000000000000000000000000000000000
--- a/lib/shove/cache/file.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-File-based cache
-
-shove's psuedo-URL for file caches follows the form:
-
-file://<path>
-
-Where the path is a URL path to a directory on a local filesystem.
-Alternatively, a native pathname to the directory can be passed as the 'engine'
-argument.
-'''
-
-import time
-
-from shove import FileBase
-from shove.cache.simple import SimpleCache
-
-
-class FileCache(FileBase, SimpleCache):
-
-    '''File-based cache backend'''
-
-    def __init__(self, engine, **kw):
-        super(FileCache, self).__init__(engine, **kw)
-
-    def __getitem__(self, key):
-        try:
-            exp, value = super(FileCache, self).__getitem__(key)
-            # Remove item if time has expired.
-            if exp < time.time():
-                del self[key]
-                raise KeyError(key)
-            return value
-        except:
-            raise KeyError(key)
-
-    def __setitem__(self, key, value):
-        if len(self) >= self._max_entries:
-            self._cull()
-        super(FileCache, self).__setitem__(
-            key, (time.time() + self.timeout, value)
-        )
-
-
-__all__ = ['FileCache']
diff --git a/lib/shove/cache/filelru.py b/lib/shove/cache/filelru.py
deleted file mode 100644
index de07661364bdf9048537d2be2ac99898e8d042e7..0000000000000000000000000000000000000000
--- a/lib/shove/cache/filelru.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-File-based LRU cache
-
-shove's psuedo-URL for file caches follows the form:
-
-file://<path>
-
-Where the path is a URL path to a directory on a local filesystem.
-Alternatively, a native pathname to the directory can be passed as the 'engine'
-argument.
-'''
-
-from shove import FileBase
-from shove.cache.simplelru import SimpleLRUCache
-
-
-class FileCache(FileBase, SimpleLRUCache):
-
-    '''File-based LRU cache backend'''
-
-
-__all__ = ['FileCache']
diff --git a/lib/shove/cache/memcached.py b/lib/shove/cache/memcached.py
deleted file mode 100644
index aedfe282ba487dd0a61ae22456a0139a392cd40a..0000000000000000000000000000000000000000
--- a/lib/shove/cache/memcached.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-"memcached" cache.
-
-The shove psuedo-URL for a memcache cache is:
-
-memcache://<memcache_server>
-'''
-
-try:
-    import memcache
-except ImportError:
-    raise ImportError("Memcache cache requires the 'memcache' library")
-
-from shove import Base
-
-
-class MemCached(Base):
-
-    '''Memcached cache backend'''
-
-    def __init__(self, engine, **kw):
-        super(MemCached, self).__init__(engine, **kw)
-        if engine.startswith('memcache://'):
-            engine = engine.split('://')[1]
-        self._store = memcache.Client(engine.split(';'))
-        # Set timeout
-        self.timeout = kw.get('timeout', 300)
-
-    def __getitem__(self, key):
-        value = self._store.get(key)
-        if value is None:
-            raise KeyError(key)
-        return self.loads(value)
-
-    def __setitem__(self, key, value):
-        self._store.set(key, self.dumps(value), self.timeout)
-
-    def __delitem__(self, key):
-        self._store.delete(key)
-
-
-__all__ = ['MemCached']
diff --git a/lib/shove/cache/memlru.py b/lib/shove/cache/memlru.py
deleted file mode 100644
index 7db61ec530a60c96fefb8058c6a819efbe238169..0000000000000000000000000000000000000000
--- a/lib/shove/cache/memlru.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Thread-safe in-memory cache using LRU.
-
-The shove psuedo-URL for a memory cache is:
-
-memlru://
-'''
-
-import copy
-import threading
-
-from shove import synchronized
-from shove.cache.simplelru import SimpleLRUCache
-
-
-class MemoryLRUCache(SimpleLRUCache):
-
-    '''Thread-safe in-memory cache backend using LRU.'''
-
-    def __init__(self, engine, **kw):
-        super(MemoryLRUCache, self).__init__(engine, **kw)
-        self._lock = threading.Condition()
-
-    @synchronized
-    def __setitem__(self, key, value):
-        super(MemoryLRUCache, self).__setitem__(key, value)
-
-    @synchronized
-    def __getitem__(self, key):
-        return copy.deepcopy(super(MemoryLRUCache, self).__getitem__(key))
-
-    @synchronized
-    def __delitem__(self, key):
-        super(MemoryLRUCache, self).__delitem__(key)
-
-
-__all__ = ['MemoryLRUCache']
diff --git a/lib/shove/cache/memory.py b/lib/shove/cache/memory.py
deleted file mode 100644
index e70f9bbb547d3915171861760a20eca6e28e1faf..0000000000000000000000000000000000000000
--- a/lib/shove/cache/memory.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Thread-safe in-memory cache.
-
-The shove psuedo-URL for a memory cache is:
-
-memory://
-'''
-
-import copy
-import threading
-
-from shove import synchronized
-from shove.cache.simple import SimpleCache
-
-
-class MemoryCache(SimpleCache):
-
-    '''Thread-safe in-memory cache backend.'''
-
-    def __init__(self, engine, **kw):
-        super(MemoryCache, self).__init__(engine, **kw)
-        self._lock = threading.Condition()
-
-    @synchronized
-    def __setitem__(self, key, value):
-        super(MemoryCache, self).__setitem__(key, value)
-
-    @synchronized
-    def __getitem__(self, key):
-        return copy.deepcopy(super(MemoryCache, self).__getitem__(key))
-
-    @synchronized
-    def __delitem__(self, key):
-        super(MemoryCache, self).__delitem__(key)
-
-
-__all__ = ['MemoryCache']
diff --git a/lib/shove/cache/redisdb.py b/lib/shove/cache/redisdb.py
deleted file mode 100644
index c53536c1565356783b3effa989df8c5f1bc71e42..0000000000000000000000000000000000000000
--- a/lib/shove/cache/redisdb.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Redis-based object cache
-
-The shove psuedo-URL for a redis cache is:
-
-redis://<host>:<port>/<db>
-'''
-
-import urlparse
-
-try:
-    import redis
-except ImportError:
-    raise ImportError('This store requires the redis library')
-
-from shove import Base
-
-
-class RedisCache(Base):
-
-    '''Redis cache backend'''
-
-    init = 'redis://'
-
-    def __init__(self, engine, **kw):
-        super(RedisCache, self).__init__(engine, **kw)
-        spliturl = urlparse.urlsplit(engine)
-        host, port = spliturl[1].split(':')
-        db = spliturl[2].replace('/', '')
-        self._store = redis.Redis(host, int(port), db)
-        # Set timeout
-        self.timeout = kw.get('timeout', 300)
-
-    def __getitem__(self, key):
-        return self.loads(self._store[key])
-
-    def __setitem__(self, key, value):
-        self._store.setex(key, self.dumps(value), self.timeout)
-
-    def __delitem__(self, key):
-        self._store.delete(key)
-
-
-__all__ = ['RedisCache']
diff --git a/lib/shove/cache/simple.py b/lib/shove/cache/simple.py
deleted file mode 100644
index 6855603ef01775a6b28b10f2e7878ea874851671..0000000000000000000000000000000000000000
--- a/lib/shove/cache/simple.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Single-process in-memory cache.
-
-The shove psuedo-URL for a simple cache is:
-
-simple://
-'''
-
-import time
-import random
-
-from shove import SimpleBase
-
-
-class SimpleCache(SimpleBase):
-
-    '''Single-process in-memory cache.'''
-
-    def __init__(self, engine, **kw):
-        super(SimpleCache, self).__init__(engine, **kw)
-        # Get random seed
-        random.seed()
-        # Set maximum number of items to cull if over max
-        self._maxcull = kw.get('maxcull', 10)
-        # Set max entries
-        self._max_entries = kw.get('max_entries', 300)
-        # Set timeout
-        self.timeout = kw.get('timeout', 300)
-
-    def __getitem__(self, key):
-        exp, value = super(SimpleCache, self).__getitem__(key)
-        # Delete if item timed out.
-        if exp < time.time():
-            super(SimpleCache, self).__delitem__(key)
-            raise KeyError(key)
-        return value
-
-    def __setitem__(self, key, value):
-        # Cull values if over max # of entries
-        if len(self) >= self._max_entries:
-            self._cull()
-        # Set expiration time and value
-        exp = time.time() + self.timeout
-        super(SimpleCache, self).__setitem__(key, (exp, value))
-
-    def _cull(self):
-        '''Remove items in cache to make room.'''
-        num, maxcull = 0, self._maxcull
-        # Cull number of items allowed (set by self._maxcull)
-        for key in self.keys():
-            # Remove only maximum # of items allowed by maxcull
-            if num <= maxcull:
-                # Remove items if expired
-                try:
-                    self[key]
-                except KeyError:
-                    num += 1
-            else:
-                break
-        # Remove any additional items up to max # of items allowed by maxcull
-        while len(self) >= self._max_entries and num <= maxcull:
-            # Cull remainder of allowed quota at random
-            del self[random.choice(self.keys())]
-            num += 1
-
-
-__all__ = ['SimpleCache']
diff --git a/lib/shove/cache/simplelru.py b/lib/shove/cache/simplelru.py
deleted file mode 100644
index fbb6e446ffb0669c8a6f7d450b039c739b03dcac..0000000000000000000000000000000000000000
--- a/lib/shove/cache/simplelru.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Single-process in-memory LRU cache.
-
-The shove psuedo-URL for a simple cache is:
-
-simplelru://
-'''
-
-from shove import LRUBase
-
-
-class SimpleLRUCache(LRUBase):
-
-    '''In-memory cache that purges based on least recently used item.'''
-
-
-__all__ = ['SimpleLRUCache']
diff --git a/lib/shove/store/__init__.py b/lib/shove/store/__init__.py
deleted file mode 100644
index 5d639a078f38487101360b5ecc031bcbd5392363..0000000000000000000000000000000000000000
--- a/lib/shove/store/__init__.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from urllib import url2pathname
-from shove.store.simple import SimpleStore
-
-
-class ClientStore(SimpleStore):
-
-    '''Base class for stores where updates have to be committed.'''
-
-    def __init__(self, engine, **kw):
-        super(ClientStore, self).__init__(engine, **kw)
-        if engine.startswith(self.init):
-            self._engine = url2pathname(engine.split('://')[1])
-
-    def __getitem__(self, key):
-        return self.loads(super(ClientStore, self).__getitem__(key))
-
-    def __setitem__(self, key, value):
-        super(ClientStore, self).__setitem__(key, self.dumps(value))
-
-
-class SyncStore(ClientStore):
-
-    '''Base class for stores where updates have to be committed.'''
-
-    def __getitem__(self, key):
-        return self.loads(super(SyncStore, self).__getitem__(key))
-
-    def __setitem__(self, key, value):
-        super(SyncStore, self).__setitem__(key, value)
-        try:
-            self.sync()
-        except AttributeError:
-            pass
-
-    def __delitem__(self, key):
-        super(SyncStore, self).__delitem__(key)
-        try:
-            self.sync()
-        except AttributeError:
-            pass
-
-
-__all__ = [
-    'bsdb', 'db', 'dbm', 'durusdb', 'file', 'ftp', 'memory', 's3', 'simple',
-    'svn', 'zodb', 'redisdb', 'hdf5db', 'leveldbstore', 'cassandra',
-]
diff --git a/lib/shove/store/bsdb.py b/lib/shove/store/bsdb.py
deleted file mode 100644
index d1f9c6dc53b534b0962f18918bf8a8becacb32ee..0000000000000000000000000000000000000000
--- a/lib/shove/store/bsdb.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Berkeley Source Database Store.
-
-shove's psuedo-URL for BSDDB stores follows the form:
-
-bsddb://<path>
-
-Where the path is a URL path to a Berkeley database. Alternatively, the native
-pathname to a Berkeley database can be passed as the 'engine' parameter.
-'''
-try:
-    import bsddb
-except ImportError:
-    raise ImportError('requires bsddb library')
-
-import threading
-
-from shove import synchronized
-from shove.store import SyncStore
-
-
-class BsdStore(SyncStore):
-
-    '''Class for Berkeley Source Database Store.'''
-
-    init = 'bsddb://'
-
-    def __init__(self, engine, **kw):
-        super(BsdStore, self).__init__(engine, **kw)
-        self._store = bsddb.hashopen(self._engine)
-        self._lock = threading.Condition()
-        self.sync = self._store.sync
-
-    @synchronized
-    def __getitem__(self, key):
-        return super(BsdStore, self).__getitem__(key)
-
-    @synchronized
-    def __setitem__(self, key, value):
-        super(BsdStore, self).__setitem__(key, value)
-
-    @synchronized
-    def __delitem__(self, key):
-        super(BsdStore, self).__delitem__(key)
-
-
-__all__ = ['BsdStore']
diff --git a/lib/shove/store/cassandra.py b/lib/shove/store/cassandra.py
deleted file mode 100644
index 1f6532ee233f53f05399299f8575fde4da46307a..0000000000000000000000000000000000000000
--- a/lib/shove/store/cassandra.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Cassandra-based object store
-
-The shove psuedo-URL for a cassandra-based store is:
-
-cassandra://<host>:<port>/<keyspace>/<columnFamily>
-'''
-
-import urlparse
-
-try:
-    import pycassa
-except ImportError:
-    raise ImportError('This store requires the pycassa library')
-
-from shove import BaseStore
-
-
-class CassandraStore(BaseStore):
-
-    '''Cassandra based store'''
-
-    init = 'cassandra://'
-
-    def __init__(self, engine, **kw):
-        super(CassandraStore, self).__init__(engine, **kw)
-        spliturl = urlparse.urlsplit(engine)
-        _, keyspace, column_family = spliturl[2].split('/')
-        try:
-            self._pool = pycassa.connect(keyspace, [spliturl[1]])
-            self._store = pycassa.ColumnFamily(self._pool, column_family)
-        except pycassa.InvalidRequestException:
-            from pycassa.system_manager import SystemManager
-            system_manager = SystemManager(spliturl[1])
-            system_manager.create_keyspace(
-                keyspace,
-                pycassa.system_manager.SIMPLE_STRATEGY,
-                {'replication_factor': str(kw.get('replication', 1))}
-            )
-            system_manager.create_column_family(keyspace, column_family)
-            self._pool = pycassa.connect(keyspace, [spliturl[1]])
-            self._store = pycassa.ColumnFamily(self._pool, column_family)
-
-    def __getitem__(self, key):
-        try:
-            item = self._store.get(key).get(key)
-            if item is not None:
-                return self.loads(item)
-            raise KeyError(key)
-        except pycassa.NotFoundException:
-            raise KeyError(key)
-
-    def __setitem__(self, key, value):
-        self._store.insert(key, dict(key=self.dumps(value)))
-
-    def __delitem__(self, key):
-        # beware eventual consistency
-        try:
-            self._store.remove(key)
-        except pycassa.NotFoundException:
-            raise KeyError(key)
-
-    def clear(self):
-        # beware eventual consistency
-        self._store.truncate()
-
-    def keys(self):
-        return list(i[0] for i in self._store.get_range())
-
-
-__all__ = ['CassandraStore']
diff --git a/lib/shove/store/db.py b/lib/shove/store/db.py
deleted file mode 100644
index 0004e6f8810cd4898f5ef8f4a2608ed31235ab12..0000000000000000000000000000000000000000
--- a/lib/shove/store/db.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Database object store.
-
-The shove psuedo-URL used for database object stores is the format used by
-SQLAlchemy:
-
-<driver>://<username>:<password>@<host>:<port>/<database>
-
-<driver> is the database engine. The engines currently supported SQLAlchemy are
-sqlite, mysql, postgres, oracle, mssql, and firebird.
-<username> is the database account user name
-<password> is the database accound password
-<host> is the database location
-<port> is the database port
-<database> is the name of the specific database
-
-For more information on specific databases see:
-
-http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_supported
-'''
-
-try:
-    from sqlalchemy import MetaData, Table, Column, String, Binary, select
-    from shove import BaseStore, DbBase
-except ImportError, e:
-    raise ImportError('Error: ' + e + ' Requires SQLAlchemy >= 0.4')
-
-
-class DbStore(BaseStore, DbBase):
-
-    '''Database cache backend.'''
-
-    def __init__(self, engine, **kw):
-        super(DbStore, self).__init__(engine, **kw)
-        # Get tablename
-        tablename = kw.get('tablename', 'store')
-        # Bind metadata
-        self._metadata = MetaData(engine)
-        # Make store table
-        self._store = Table(tablename, self._metadata,
-            Column('key', String(255), primary_key=True, nullable=False),
-            Column('value', Binary, nullable=False),
-        )
-        # Create store table if it does not exist
-        if not self._store.exists():
-            self._store.create()
-
-    def __getitem__(self, key):
-        row = select(
-            [self._store.c.value], self._store.c.key == key,
-        ).execute().fetchone()
-        if row is not None:
-            return self.loads(str(row.value))
-        raise KeyError(key)
-
-    def __setitem__(self, k, v):
-        v, store = self.dumps(v), self._store
-        # Update database if key already present
-        if k in self:
-            store.update(store.c.key == k).execute(value=v)
-        # Insert new key if key not present
-        else:
-            store.insert().execute(key=k, value=v)
-
-    def keys(self):
-        '''Returns a list of keys in the store.'''
-        return list(i[0] for i in select(
-            [self._store.c.key]
-        ).execute().fetchall())
-
-
-__all__ = ['DbStore']
diff --git a/lib/shove/store/dbm.py b/lib/shove/store/dbm.py
deleted file mode 100644
index 323d24847708ca8e1996c32277ab381c2c04ef89..0000000000000000000000000000000000000000
--- a/lib/shove/store/dbm.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-DBM Database Store.
-
-shove's psuedo-URL for DBM stores follows the form:
-
-dbm://<path>
-
-Where <path> is a URL path to a DBM database. Alternatively, the native
-pathname to a DBM database can be passed as the 'engine' parameter.
-'''
-
-import anydbm
-
-from shove.store import SyncStore
-
-
-class DbmStore(SyncStore):
-
-    '''Class for variants of the DBM database.'''
-
-    init = 'dbm://'
-
-    def __init__(self, engine, **kw):
-        super(DbmStore, self).__init__(engine, **kw)
-        self._store = anydbm.open(self._engine, 'c')
-        try:
-            self.sync = self._store.sync
-        except AttributeError:
-            pass
-
-
-__all__ = ['DbmStore']
diff --git a/lib/shove/store/durusdb.py b/lib/shove/store/durusdb.py
deleted file mode 100644
index 8e27670e65b4da4b8bb8697f670598643dd6d164..0000000000000000000000000000000000000000
--- a/lib/shove/store/durusdb.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Durus object database frontend.
-
-shove's psuedo-URL for Durus stores follows the form:
-
-durus://<path>
-
-
-Where the path is a URL path to a durus FileStorage database. Alternatively, a
-native pathname to a durus database can be passed as the 'engine' parameter.
-'''
-
-try:
-    from durus.connection import Connection
-    from durus.file_storage import FileStorage
-except ImportError:
-    raise ImportError('Requires Durus library')
-
-from shove.store import SyncStore
-
-
-class DurusStore(SyncStore):
-
-    '''Class for Durus object database frontend.'''
-
-    init = 'durus://'
-
-    def __init__(self, engine, **kw):
-        super(DurusStore, self).__init__(engine, **kw)
-        self._db = FileStorage(self._engine)
-        self._connection = Connection(self._db)
-        self.sync = self._connection.commit
-        self._store = self._connection.get_root()
-
-    def close(self):
-        '''Closes all open storage and connections.'''
-        self.sync()
-        self._db.close()
-        super(DurusStore, self).close()
-
-
-__all__ = ['DurusStore']
diff --git a/lib/shove/store/file.py b/lib/shove/store/file.py
deleted file mode 100644
index e66e9c4f1219de62ebfd3232670110c26a67e80f..0000000000000000000000000000000000000000
--- a/lib/shove/store/file.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Filesystem-based object store
-
-shove's psuedo-URL for filesystem-based stores follows the form:
-
-file://<path>
-
-Where the path is a URL path to a directory on a local filesystem.
-Alternatively, a native pathname to the directory can be passed as the 'engine'
-argument.
-'''
-
-from shove import BaseStore, FileBase
-
-
-class FileStore(FileBase, BaseStore):
-
-    '''File-based store.'''
-
-    def __init__(self, engine, **kw):
-        super(FileStore, self).__init__(engine, **kw)
-
-
-__all__ = ['FileStore']
diff --git a/lib/shove/store/ftp.py b/lib/shove/store/ftp.py
deleted file mode 100644
index c2d4aec60c395bc788535e3f4c61143ec9a12489..0000000000000000000000000000000000000000
--- a/lib/shove/store/ftp.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-FTP-accessed stores
-
-shove's URL for FTP accessed stores follows the standard form for FTP URLs
-defined in RFC-1738:
-
-ftp://<user>:<password>@<host>:<port>/<url-path>
-'''
-
-import urlparse
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
-from ftplib import FTP, error_perm
-
-from shove import BaseStore
-
-
-class FtpStore(BaseStore):
-
-    def __init__(self, engine, **kw):
-        super(FtpStore, self).__init__(engine, **kw)
-        user = kw.get('user', 'anonymous')
-        password = kw.get('password', '')
-        spliturl = urlparse.urlsplit(engine)
-        # Set URL, path, and strip 'ftp://' off
-        base, path = spliturl[1], spliturl[2] + '/'
-        if '@' in base:
-            auth, base = base.split('@')
-            user, password = auth.split(':')
-        self._store = FTP(base, user, password)
-        # Change to remote path if it exits
-        try:
-            self._store.cwd(path)
-        except error_perm:
-            self._makedir(path)
-        self._base, self._user, self._password = base, user, password
-        self._updated, self ._keys = True, None
-
-    def __getitem__(self, key):
-        try:
-            local = StringIO()
-            # Download item
-            self._store.retrbinary('RETR %s' % key, local.write)
-            self._updated = False
-            return self.loads(local.getvalue())
-        except:
-            raise KeyError(key)
-
-    def __setitem__(self, key, value):
-        local = StringIO(self.dumps(value))
-        self._store.storbinary('STOR %s' % key, local)
-        self._updated = True
-
-    def __delitem__(self, key):
-        try:
-            self._store.delete(key)
-            self._updated = True
-        except:
-            raise KeyError(key)
-
-    def _makedir(self, path):
-        '''Makes remote paths on an FTP server.'''
-        paths = list(reversed([i for i in path.split('/') if i != '']))
-        while paths:
-            tpath = paths.pop()
-            self._store.mkd(tpath)
-            self._store.cwd(tpath)
-
-    def keys(self):
-        '''Returns a list of keys in a store.'''
-        if self._updated or self._keys is None:
-            rlist, nlist = list(), list()
-            # Remote directory listing
-            self._store.retrlines('LIST -a', rlist.append)
-            for rlisting in rlist:
-                # Split remote file based on whitespace
-                rfile = rlisting.split()
-                # Append tuple of remote item type & name
-                if rfile[-1] not in ('.', '..') and rfile[0].startswith('-'):
-                    nlist.append(rfile[-1])
-            self._keys = nlist
-        return self._keys
-
-
-__all__ = ['FtpStore']
diff --git a/lib/shove/store/hdf5.py b/lib/shove/store/hdf5.py
deleted file mode 100644
index a9b618e516953bfd65a24085780a69dcac4e22b4..0000000000000000000000000000000000000000
--- a/lib/shove/store/hdf5.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-HDF5 Database Store.
-
-shove's psuedo-URL for HDF5 stores follows the form:
-
-hdf5://<path>/<group>
-
-Where <path> is a URL path to a HDF5 database. Alternatively, the native
-pathname to a HDF5 database can be passed as the 'engine' parameter.
-<group> is the name of the database.
-'''
-
-try:
-    import h5py
-except ImportError:
-    raise ImportError('This store requires h5py library')
-
-from shove.store import ClientStore
-
-
-class HDF5Store(ClientStore):
-
-    '''LevelDB based store'''
-
-    init = 'hdf5://'
-
-    def __init__(self, engine, **kw):
-        super(HDF5Store, self).__init__(engine, **kw)
-        engine, group = self._engine.rsplit('/')
-        self._store = h5py.File(engine).require_group(group).attrs
-
-
-__all__ = ['HDF5Store']
diff --git a/lib/shove/store/leveldbstore.py b/lib/shove/store/leveldbstore.py
deleted file mode 100644
index ca73a494e1cf56fa636d81861656612e0eb0a6a7..0000000000000000000000000000000000000000
--- a/lib/shove/store/leveldbstore.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-LevelDB Database Store.
-
-shove's psuedo-URL for LevelDB stores follows the form:
-
-leveldb://<path>
-
-Where <path> is a URL path to a LevelDB database. Alternatively, the native
-pathname to a LevelDB database can be passed as the 'engine' parameter.
-'''
-
-try:
-    import leveldb
-except ImportError:
-    raise ImportError('This store requires py-leveldb library')
-
-from shove.store import ClientStore
-
-
-class LevelDBStore(ClientStore):
-
-    '''LevelDB based store'''
-
-    init = 'leveldb://'
-
-    def __init__(self, engine, **kw):
-        super(LevelDBStore, self).__init__(engine, **kw)
-        self._store = leveldb.LevelDB(self._engine)
-
-    def __getitem__(self, key):
-        item = self.loads(self._store.Get(key))
-        if item is not None:
-            return item
-        raise KeyError(key)
-
-    def __setitem__(self, key, value):
-        self._store.Put(key, self.dumps(value))
-
-    def __delitem__(self, key):
-        self._store.Delete(key)
-
-    def keys(self):
-        return list(k for k in self._store.RangeIter(include_value=False))
-
-
-__all__ = ['LevelDBStore']
diff --git a/lib/shove/store/memory.py b/lib/shove/store/memory.py
deleted file mode 100644
index 525ae69ef5ec3b156e5bad400be2972f1a12650d..0000000000000000000000000000000000000000
--- a/lib/shove/store/memory.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Thread-safe in-memory store.
-
-The shove psuedo-URL for a memory store is:
-
-memory://
-'''
-
-import copy
-import threading
-
-from shove import synchronized
-from shove.store.simple import SimpleStore
-
-
-class MemoryStore(SimpleStore):
-
-    '''Thread-safe in-memory store.'''
-
-    def __init__(self, engine, **kw):
-        super(MemoryStore, self).__init__(engine, **kw)
-        self._lock = threading.Condition()
-
-    @synchronized
-    def __getitem__(self, key):
-        return copy.deepcopy(super(MemoryStore, self).__getitem__(key))
-
-    @synchronized
-    def __setitem__(self, key, value):
-        super(MemoryStore, self).__setitem__(key, value)
-
-    @synchronized
-    def __delitem__(self, key):
-        super(MemoryStore, self).__delitem__(key)
-
-
-__all__ = ['MemoryStore']
diff --git a/lib/shove/store/redisdb.py b/lib/shove/store/redisdb.py
deleted file mode 100644
index 67fa2ebd1af254acc738743bc972759e4c115c86..0000000000000000000000000000000000000000
--- a/lib/shove/store/redisdb.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Redis-based object store
-
-The shove psuedo-URL for a redis-based store is:
-
-redis://<host>:<port>/<db>
-'''
-
-import urlparse
-
-try:
-    import redis
-except ImportError:
-    raise ImportError('This store requires the redis library')
-
-from shove.store import ClientStore
-
-
-class RedisStore(ClientStore):
-
-    '''Redis based store'''
-
-    init = 'redis://'
-
-    def __init__(self, engine, **kw):
-        super(RedisStore, self).__init__(engine, **kw)
-        spliturl = urlparse.urlsplit(engine)
-        host, port = spliturl[1].split(':')
-        db = spliturl[2].replace('/', '')
-        self._store = redis.Redis(host, int(port), db)
-
-    def __contains__(self, key):
-        return self._store.exists(key)
-
-    def clear(self):
-        self._store.flushdb()
-
-    def keys(self):
-        return self._store.keys()
-
-    def setdefault(self, key, default=None):
-        return self._store.getset(key, default)
-
-    def update(self, other=None, **kw):
-        args = kw if other is not None else other
-        self._store.mset(args)
-
-
-__all__ = ['RedisStore']
diff --git a/lib/shove/store/s3.py b/lib/shove/store/s3.py
deleted file mode 100644
index dbf12f213a45e2e674bfe86c327ac07fa6eb5a00..0000000000000000000000000000000000000000
--- a/lib/shove/store/s3.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-S3-accessed stores
-
-shove's psuedo-URL for stores found on Amazon.com's S3 web service follows this
-form:
-
-s3://<s3_key>:<s3_secret>@<bucket>
-
-<s3_key> is the Access Key issued by Amazon
-<s3_secret> is the Secret Access Key issued by Amazon
-<bucket> is the name of the bucket accessed through the S3 service
-'''
-
-try:
-    from boto.s3.connection import S3Connection
-    from boto.s3.key import Key
-except ImportError:
-    raise ImportError('Requires boto library')
-
-from shove import BaseStore
-
-
-class S3Store(BaseStore):
-
-    def __init__(self, engine=None, **kw):
-        super(S3Store, self).__init__(engine, **kw)
-        # key = Access Key, secret=Secret Access Key, bucket=bucket name
-        key, secret, bucket = kw.get('key'), kw.get('secret'), kw.get('bucket')
-        if engine is not None:
-            auth, bucket = engine.split('://')[1].split('@')
-            key, secret = auth.split(':')
-        # kw 'secure' = (True or False, use HTTPS)
-        self._conn = S3Connection(key, secret, kw.get('secure', False))
-        buckets = self._conn.get_all_buckets()
-        # Use bucket if it exists
-        for b in buckets:
-            if b.name == bucket:
-                self._store = b
-                break
-        # Create bucket if it doesn't exist
-        else:
-            self._store = self._conn.create_bucket(bucket)
-        # Set bucket permission ('private', 'public-read',
-        # 'public-read-write', 'authenticated-read'
-        self._store.set_acl(kw.get('acl', 'private'))
-        # Updated flag used for avoiding network calls
-        self._updated, self._keys = True, None
-
-    def __getitem__(self, key):
-        rkey = self._store.lookup(key)
-        if rkey is None:
-            raise KeyError(key)
-        # Fetch string
-        value = self.loads(rkey.get_contents_as_string())
-        # Flag that the store has not been updated
-        self._updated = False
-        return value
-
-    def __setitem__(self, key, value):
-        rkey = Key(self._store)
-        rkey.key = key
-        rkey.set_contents_from_string(self.dumps(value))
-        # Flag that the store has been updated
-        self._updated = True
-
-    def __delitem__(self, key):
-        try:
-            self._store.delete_key(key)
-            # Flag that the store has been updated
-            self._updated = True
-        except:
-            raise KeyError(key)
-
-    def keys(self):
-        '''Returns a list of keys in the store.'''
-        return list(i[0] for i in self.items())
-
-    def items(self):
-        '''Returns a list of items from the store.'''
-        if self._updated or self._keys is None:
-            self._keys = self._store.get_all_keys()
-        return list((str(k.key), k) for k in self._keys)
-
-    def iteritems(self):
-        '''Lazily returns items from the store.'''
-        for k in self.items():
-            yield (k.key, k)
-
-
-__all__ = ['S3Store']
diff --git a/lib/shove/store/simple.py b/lib/shove/store/simple.py
deleted file mode 100644
index 8f7ebb33488a63ea8b3dfc2fd2e583f92b7abb7e..0000000000000000000000000000000000000000
--- a/lib/shove/store/simple.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Single-process in-memory store.
-
-The shove psuedo-URL for a simple store is:
-
-simple://
-'''
-
-from shove import BaseStore, SimpleBase
-
-
-class SimpleStore(SimpleBase, BaseStore):
-
-    '''Single-process in-memory store.'''
-
-    def __init__(self, engine, **kw):
-        super(SimpleStore, self).__init__(engine, **kw)
-
-
-__all__ = ['SimpleStore']
diff --git a/lib/shove/store/svn.py b/lib/shove/store/svn.py
deleted file mode 100644
index 5bb8c33e29afd8929f3ce4daf1f957ae853d443c..0000000000000000000000000000000000000000
--- a/lib/shove/store/svn.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-subversion managed store.
-
-The shove psuedo-URL used for a subversion store that is password protected is:
-
-svn:<username><password>:<path>?url=<url>
-
-or for non-password protected repositories:
-
-svn://<path>?url=<url>
-
-<path> is the local repository copy
-<url> is the URL of the subversion repository
-'''
-
-import os
-import urllib
-import threading
-
-try:
-    import pysvn
-except ImportError:
-    raise ImportError('Requires Python Subversion library')
-
-from shove import BaseStore, synchronized
-
-
-class SvnStore(BaseStore):
-
-    '''Class for subversion store.'''
-
-    def __init__(self, engine=None, **kw):
-        super(SvnStore, self).__init__(engine, **kw)
-        # Get path, url from keywords if used
-        path, url = kw.get('path'), kw.get('url')
-        # Get username. password from keywords if used
-        user, password = kw.get('user'), kw.get('password')
-        # Process psuedo URL if used
-        if engine is not None:
-            path, query = engine.split('n://')[1].split('?')
-            url = query.split('=')[1]
-            # Check for username, password
-            if '@' in path:
-                auth, path = path.split('@')
-                user, password = auth.split(':')
-            path = urllib.url2pathname(path)
-        # Create subversion client
-        self._client = pysvn.Client()
-        # Assign username, password
-        if user is not None:
-            self._client.set_username(user)
-        if password is not None:
-            self._client.set_password(password)
-        # Verify that store exists in repository
-        try:
-            self._client.info2(url)
-        # Create store in repository if it doesn't exist
-        except pysvn.ClientError:
-            self._client.mkdir(url, 'Adding directory')
-        # Verify that local copy exists
-        try:
-            if self._client.info(path) is None:
-                self._client.checkout(url, path)
-        # Check it out if it doesn't exist
-        except pysvn.ClientError:
-            self._client.checkout(url, path)
-        self._path, self._url = path, url
-        # Lock
-        self._lock = threading.Condition()
-
-    @synchronized
-    def __getitem__(self, key):
-        try:
-            return self.loads(self._client.cat(self._key_to_file(key)))
-        except:
-            raise KeyError(key)
-
-    @synchronized
-    def __setitem__(self, key, value):
-        fname = self._key_to_file(key)
-        # Write value to file
-        open(fname, 'wb').write(self.dumps(value))
-        # Add to repository
-        if key not in self:
-            self._client.add(fname)
-        self._client.checkin([fname], 'Adding %s' % fname)
-
-    @synchronized
-    def __delitem__(self, key):
-        try:
-            fname = self._key_to_file(key)
-            self._client.remove(fname)
-            # Remove deleted value from repository
-            self._client.checkin([fname], 'Removing %s' % fname)
-        except:
-            raise KeyError(key)
-
-    def _key_to_file(self, key):
-        '''Gives the filesystem path for a key.'''
-        return os.path.join(self._path, urllib.quote_plus(key))
-
-    @synchronized
-    def keys(self):
-        '''Returns a list of keys in the subversion repository.'''
-        return list(str(i.name.split('/')[-1]) for i
-            in self._client.ls(self._path))
-
-
-__all__ = ['SvnStore']
diff --git a/lib/shove/store/zodb.py b/lib/shove/store/zodb.py
deleted file mode 100644
index 43768dde1e5a8b0705f660c655b5a08514879453..0000000000000000000000000000000000000000
--- a/lib/shove/store/zodb.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-'''
-Zope Object Database store frontend.
-
-shove's psuedo-URL for ZODB stores follows the form:
-
-zodb:<path>
-
-
-Where the path is a URL path to a ZODB FileStorage database. Alternatively, a
-native pathname to a ZODB database can be passed as the 'engine' argument.
-'''
-
-try:
-    import transaction
-    from ZODB import FileStorage, DB
-except ImportError:
-    raise ImportError('Requires ZODB library')
-
-from shove.store import SyncStore
-
-
-class ZodbStore(SyncStore):
-
-    '''ZODB store front end.'''
-
-    init = 'zodb://'
-
-    def __init__(self, engine, **kw):
-        super(ZodbStore, self).__init__(engine, **kw)
-        # Handle psuedo-URL
-        self._storage = FileStorage.FileStorage(self._engine)
-        self._db = DB(self._storage)
-        self._connection = self._db.open()
-        self._store = self._connection.root()
-        # Keeps DB in synch through commits of transactions
-        self.sync = transaction.commit
-
-    def close(self):
-        '''Closes all open storage and connections.'''
-        self.sync()
-        super(ZodbStore, self).close()
-        self._connection.close()
-        self._db.close()
-        self._storage.close()
-
-
-__all__ = ['ZodbStore']
diff --git a/lib/shove/tests/__init__.py b/lib/shove/tests/__init__.py
deleted file mode 100644
index 40a96afc6ff09d58a702b76e3f7dd412fe975e26..0000000000000000000000000000000000000000
--- a/lib/shove/tests/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
diff --git a/lib/shove/tests/test_bsddb_store.py b/lib/shove/tests/test_bsddb_store.py
deleted file mode 100644
index 3de7896edab4e766b457c6edaf0bffd134d2c37b..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_bsddb_store.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestBsdbStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('bsddb://test.db', compress=True)
-
-    def tearDown(self):
-        import os
-        self.store.close()
-        os.remove('test.db')
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_cassandra_store.py b/lib/shove/tests/test_cassandra_store.py
deleted file mode 100644
index a5c60f6a1f65aa4ebbd4d2d01685a5d34e5120c4..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_cassandra_store.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestCassandraStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        from pycassa.system_manager import SystemManager
-        system_manager = SystemManager('localhost:9160')
-        try:
-            system_manager.create_column_family('Foo', 'shove')
-        except:
-            pass
-        self.store = Shove('cassandra://localhost:9160/Foo/shove')
-
-    def tearDown(self):
-        self.store.clear()
-        self.store.close()
-        from pycassa.system_manager import SystemManager
-        system_manager = SystemManager('localhost:9160')
-        system_manager.drop_column_family('Foo', 'shove')
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-#    def test_clear(self):
-#        self.store['max'] = 3
-#        self.store['min'] = 6
-#        self.store['pow'] = 7
-#        self.store.clear()
-#        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-#    def test_popitem(self):
-#        self.store['max'] = 3
-#        self.store['min'] = 6
-#        self.store['pow'] = 7
-#        item = self.store.popitem()
-#        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-#        self.store['pow'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store.setdefault('pow', 8), 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_db_cache.py b/lib/shove/tests/test_db_cache.py
deleted file mode 100644
index 9dd27a0648052fe161cc0d1706d56663d68a2f83..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_db_cache.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestDbCache(unittest.TestCase):
-
-    initstring = 'sqlite:///'
-
-    def setUp(self):
-        from shove.cache.db import DbCache
-        self.cache = DbCache(self.initstring)
-
-    def tearDown(self):
-        self.cache = None
-
-    def test_getitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_setitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_delitem(self):
-        self.cache['test'] = 'test'
-        del self.cache['test']
-        self.assertEqual('test' in self.cache, False)
-
-    def test_get(self):
-        self.assertEqual(self.cache.get('min'), None)
-
-    def test_timeout(self):
-        import time
-        from shove.cache.db import DbCache
-        cache = DbCache(self.initstring, timeout=1)
-        cache['test'] = 'test'
-        time.sleep(2)
-
-        def tmp():
-            cache['test']
-        self.assertRaises(KeyError, tmp)
-
-    def test_cull(self):
-        from shove.cache.db import DbCache
-        cache = DbCache(self.initstring, max_entries=1)
-        cache['test'] = 'test'
-        cache['test2'] = 'test'
-        cache['test2'] = 'test'
-        self.assertEquals(len(cache), 1)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_db_store.py b/lib/shove/tests/test_db_store.py
deleted file mode 100644
index 1d9ad61689bc275f568c6fed2025b7fbb5213f4d..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_db_store.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestDbStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('sqlite://', compress=True)
-
-    def tearDown(self):
-        self.store.close()
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_dbm_store.py b/lib/shove/tests/test_dbm_store.py
deleted file mode 100644
index e64ac9e7e7b74d2c05a76e533fc43ef1b891505f..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_dbm_store.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestDbmStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('dbm://test.dbm', compress=True)
-
-    def tearDown(self):
-        import os
-        self.store.close()
-        try:
-            os.remove('test.dbm.db')
-        except OSError:
-            pass
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.setdefault('how', 8)
-        self.assertEqual(self.store['how'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_durus_store.py b/lib/shove/tests/test_durus_store.py
deleted file mode 100644
index 006fcc416b46e5f5fc5a0673deae2f22b9853313..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_durus_store.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestDurusStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('durus://test.durus', compress=True)
-
-    def tearDown(self):
-        import os
-        self.store.close()
-        os.remove('test.durus')
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_file_cache.py b/lib/shove/tests/test_file_cache.py
deleted file mode 100644
index b288ce82f6775234fd7b99fbb2d1e0f7c8056923..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_file_cache.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestFileCache(unittest.TestCase):
-
-    initstring = 'file://test'
-
-    def setUp(self):
-        from shove.cache.file import FileCache
-        self.cache = FileCache(self.initstring)
-
-    def tearDown(self):
-        import os
-        self.cache = None
-        for x in os.listdir('test'):
-            os.remove(os.path.join('test', x))
-        os.rmdir('test')
-
-    def test_getitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_setitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_delitem(self):
-        self.cache['test'] = 'test'
-        del self.cache['test']
-        self.assertEqual('test' in self.cache, False)
-
-    def test_get(self):
-        self.assertEqual(self.cache.get('min'), None)
-
-    def test_timeout(self):
-        import time
-        from shove.cache.file import FileCache
-        cache = FileCache(self.initstring, timeout=1)
-        cache['test'] = 'test'
-        time.sleep(2)
-
-        def tmp():
-            cache['test']
-        self.assertRaises(KeyError, tmp)
-
-    def test_cull(self):
-        from shove.cache.file import FileCache
-        cache = FileCache(self.initstring, max_entries=1)
-        cache['test'] = 'test'
-        cache['test2'] = 'test'
-        num = len(cache)
-        self.assertEquals(num, 1)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_file_store.py b/lib/shove/tests/test_file_store.py
deleted file mode 100644
index 35643ced1a9ce061a8986bd1952ee512b709d1aa..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_file_store.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestFileStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('file://test', compress=True)
-
-    def tearDown(self):
-        import os
-        self.store.close()
-        for x in os.listdir('test'):
-            os.remove(os.path.join('test', x))
-        os.rmdir('test')
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.store.sync()
-        tstore.sync()
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_ftp_store.py b/lib/shove/tests/test_ftp_store.py
deleted file mode 100644
index 17679a2ccdc8da70e787024a70380b3f07087b52..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_ftp_store.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestFtpStore(unittest.TestCase):
-
-    ftpstring = 'put ftp string here'
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove(self.ftpstring, compress=True)
-
-    def tearDown(self):
-        self.store.clear()
-        self.store.close()
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.store.sync()
-        tstore.sync()
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store.sync()
-        self.assertEqual(len(self.store), 2)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store.sync()
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        item = self.store.popitem()
-        self.store.sync()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.store.sync()
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.sync()
-        self.store.update(tstore)
-        self.store.sync()
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_hdf5_store.py b/lib/shove/tests/test_hdf5_store.py
deleted file mode 100644
index b1342ecf64a3f0d99a47feb6f9dd268d9fb31491..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_hdf5_store.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest2
-
-
-class TestHDF5Store(unittest2.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('hdf5://test.hdf5/test')
-
-    def tearDown(self):
-        import os
-        self.store.close()
-        try:
-            os.remove('test.hdf5')
-        except OSError:
-            pass
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.setdefault('bow', 8)
-        self.assertEqual(self.store['bow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-if __name__ == '__main__':
-    unittest2.main()
diff --git a/lib/shove/tests/test_leveldb_store.py b/lib/shove/tests/test_leveldb_store.py
deleted file mode 100644
index b3a3d1773cfc26c706d5fd9aaccccfc1e3a0cbec..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_leveldb_store.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest2
-
-
-class TestLevelDBStore(unittest2.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('leveldb://test', compress=True)
-
-    def tearDown(self):
-        import shutil
-        shutil.rmtree('test')
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.setdefault('bow', 8)
-        self.assertEqual(self.store['bow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest2.main()
diff --git a/lib/shove/tests/test_memcached_cache.py b/lib/shove/tests/test_memcached_cache.py
deleted file mode 100644
index 98f0b96d928bd0a06793dd38da0639e5f0338fb5..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_memcached_cache.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestMemcached(unittest.TestCase):
-
-    initstring = 'memcache://localhost:11211'
-
-    def setUp(self):
-        from shove.cache.memcached import MemCached
-        self.cache = MemCached(self.initstring)
-
-    def tearDown(self):
-        self.cache = None
-
-    def test_getitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_setitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_delitem(self):
-        self.cache['test'] = 'test'
-        del self.cache['test']
-        self.assertEqual('test' in self.cache, False)
-
-    def test_get(self):
-        self.assertEqual(self.cache.get('min'), None)
-
-    def test_timeout(self):
-        import time
-        from shove.cache.memcached import MemCached
-        cache = MemCached(self.initstring, timeout=1)
-        cache['test'] = 'test'
-        time.sleep(1)
-
-        def tmp():
-            cache['test']
-        self.assertRaises(KeyError, tmp)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_memory_cache.py b/lib/shove/tests/test_memory_cache.py
deleted file mode 100644
index 87749cdbfcee4880bd2655eafc12315f252025dc..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_memory_cache.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestMemoryCache(unittest.TestCase):
-
-    initstring = 'memory://'
-
-    def setUp(self):
-        from shove.cache.memory import MemoryCache
-        self.cache = MemoryCache(self.initstring)
-
-    def tearDown(self):
-        self.cache = None
-
-    def test_getitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_setitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_delitem(self):
-        self.cache['test'] = 'test'
-        del self.cache['test']
-        self.assertEqual('test' in self.cache, False)
-
-    def test_get(self):
-        self.assertEqual(self.cache.get('min'), None)
-
-    def test_timeout(self):
-        import time
-        from shove.cache.memory import MemoryCache
-        cache = MemoryCache(self.initstring, timeout=1)
-        cache['test'] = 'test'
-        time.sleep(1)
-
-        def tmp():
-            cache['test']
-        self.assertRaises(KeyError, tmp)
-
-    def test_cull(self):
-        from shove.cache.memory import MemoryCache
-        cache = MemoryCache(self.initstring, max_entries=1)
-        cache['test'] = 'test'
-        cache['test2'] = 'test'
-        cache['test2'] = 'test'
-        self.assertEquals(len(cache), 1)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_memory_store.py b/lib/shove/tests/test_memory_store.py
deleted file mode 100644
index 12e505dd5c50c29e11dd771912fec258524dd935..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_memory_store.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestMemoryStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('memory://', compress=True)
-
-    def tearDown(self):
-        self.store.close()
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.store.sync()
-        tstore.sync()
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_redis_cache.py b/lib/shove/tests/test_redis_cache.py
deleted file mode 100644
index c8e9b8db93030a744ac8b79d64169c8efb0c158d..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_redis_cache.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestRedisCache(unittest.TestCase):
-
-    initstring = 'redis://localhost:6379/0'
-
-    def setUp(self):
-        from shove.cache.redisdb import RedisCache
-        self.cache = RedisCache(self.initstring)
-
-    def tearDown(self):
-        self.cache = None
-
-    def test_getitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_setitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_delitem(self):
-        self.cache['test'] = 'test'
-        del self.cache['test']
-        self.assertEqual('test' in self.cache, False)
-
-    def test_get(self):
-        self.assertEqual(self.cache.get('min'), None)
-
-    def test_timeout(self):
-        import time
-        from shove.cache.redisdb import RedisCache
-        cache = RedisCache(self.initstring, timeout=1)
-        cache['test'] = 'test'
-        time.sleep(3)
-        def tmp(): #@IgnorePep8
-            return cache['test']
-        self.assertRaises(KeyError, tmp)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_redis_store.py b/lib/shove/tests/test_redis_store.py
deleted file mode 100644
index 06b1e0e9cb221c520524cee06698d2bce0807dba..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_redis_store.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestRedisStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('redis://localhost:6379/0')
-
-    def tearDown(self):
-        self.store.clear()
-        self.store.close()
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store.setdefault('pow', 8), 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_s3_store.py b/lib/shove/tests/test_s3_store.py
deleted file mode 100644
index 8a0f08d765fc9f12b0057805061ade6584a27d5a..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_s3_store.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestS3Store(unittest.TestCase):
-
-    s3string = 's3 test string here'
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove(self.s3string, compress=True)
-
-    def tearDown(self):
-        self.store.clear()
-        self.store.close()
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.store.sync()
-        tstore.sync()
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store.sync()
-        self.assertEqual(len(self.store), 2)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store.sync()
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        item = self.store.popitem()
-        self.store.sync()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.store.sync()
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.sync()
-        self.store.update(tstore)
-        self.store.sync()
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_simple_cache.py b/lib/shove/tests/test_simple_cache.py
deleted file mode 100644
index 8cd1830cee422d452cedd24f05dee78a36f8d167..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_simple_cache.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestSimpleCache(unittest.TestCase):
-
-    initstring = 'simple://'
-
-    def setUp(self):
-        from shove.cache.simple import SimpleCache
-        self.cache = SimpleCache(self.initstring)
-
-    def tearDown(self):
-        self.cache = None
-
-    def test_getitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_setitem(self):
-        self.cache['test'] = 'test'
-        self.assertEqual(self.cache['test'], 'test')
-
-    def test_delitem(self):
-        self.cache['test'] = 'test'
-        del self.cache['test']
-        self.assertEqual('test' in self.cache, False)
-
-    def test_get(self):
-        self.assertEqual(self.cache.get('min'), None)
-
-    def test_timeout(self):
-        import time
-        from shove.cache.simple import SimpleCache
-        cache = SimpleCache(self.initstring, timeout=1)
-        cache['test'] = 'test'
-        time.sleep(1)
-
-        def tmp():
-            cache['test']
-        self.assertRaises(KeyError, tmp)
-
-    def test_cull(self):
-        from shove.cache.simple import SimpleCache
-        cache = SimpleCache(self.initstring, max_entries=1)
-        cache['test'] = 'test'
-        cache['test2'] = 'test'
-        cache['test2'] = 'test'
-        self.assertEquals(len(cache), 1)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_simple_store.py b/lib/shove/tests/test_simple_store.py
deleted file mode 100644
index d2431ec5ba428c4a8f8ec4fc410f53dc7562018a..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_simple_store.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestSimpleStore(unittest.TestCase):
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove('simple://', compress=True)
-
-    def tearDown(self):
-        self.store.close()
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.store.sync()
-        tstore.sync()
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_svn_store.py b/lib/shove/tests/test_svn_store.py
deleted file mode 100644
index b31038161b2be929278cc516a3c345840073fd5e..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_svn_store.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestSvnStore(unittest.TestCase):
-
-    svnstring = 'SVN test string here'
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove(self.svnstring, compress=True)
-
-    def tearDown(self):
-        self.store.clear()
-        self.store.close()
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.store.sync()
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.store.sync()
-        tstore.sync()
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store.sync()
-        self.assertEqual(len(self.store), 2)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store.sync()
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        item = self.store.popitem()
-        self.store.sync()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.store.sync()
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.sync()
-        self.store.update(tstore)
-        self.store.sync()
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.sync()
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/shove/tests/test_zodb_store.py b/lib/shove/tests/test_zodb_store.py
deleted file mode 100644
index 9d979fea0b54d6a78d9bd895cd2f40b7970ede71..0000000000000000000000000000000000000000
--- a/lib/shove/tests/test_zodb_store.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-
-class TestZodbStore(unittest.TestCase):
-
-    init = 'zodb://test.db'
-
-    def setUp(self):
-        from shove import Shove
-        self.store = Shove(self.init, compress=True)
-
-    def tearDown(self):
-        self.store.close()
-        import os
-        os.remove('test.db')
-        os.remove('test.db.index')
-        os.remove('test.db.tmp')
-        os.remove('test.db.lock')
-
-    def test__getitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__setitem__(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store['max'], 3)
-
-    def test__delitem__(self):
-        self.store['max'] = 3
-        del self.store['max']
-        self.assertEqual('max' in self.store, False)
-
-    def test_get(self):
-        self.store['max'] = 3
-        self.assertEqual(self.store.get('min'), None)
-
-    def test__cmp__(self):
-        from shove import Shove
-        tstore = Shove()
-        self.store['max'] = 3
-        tstore['max'] = 3
-        self.assertEqual(self.store, tstore)
-
-    def test__len__(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.assertEqual(len(self.store), 2)
-
-    def test_close(self):
-        self.store.close()
-        self.assertEqual(self.store, None)
-
-    def test_clear(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        self.store.clear()
-        self.assertEqual(len(self.store), 0)
-
-    def test_items(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.items())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iteritems(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iteritems())
-        self.assertEqual(('min', 6) in slist, True)
-
-    def test_iterkeys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.iterkeys())
-        self.assertEqual('min' in slist, True)
-
-    def test_itervalues(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = list(self.store.itervalues())
-        self.assertEqual(6 in slist, True)
-
-    def test_pop(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        item = self.store.pop('min')
-        self.assertEqual(item, 6)
-
-    def test_popitem(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        item = self.store.popitem()
-        self.assertEqual(len(item) + len(self.store), 4)
-
-    def test_setdefault(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['powl'] = 7
-        self.store.setdefault('pow', 8)
-        self.assertEqual(self.store['pow'], 8)
-
-    def test_update(self):
-        from shove import Shove
-        tstore = Shove()
-        tstore['max'] = 3
-        tstore['min'] = 6
-        tstore['pow'] = 7
-        self.store['max'] = 2
-        self.store['min'] = 3
-        self.store['pow'] = 7
-        self.store.update(tstore)
-        self.assertEqual(self.store['min'], 6)
-
-    def test_values(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.values()
-        self.assertEqual(6 in slist, True)
-
-    def test_keys(self):
-        self.store['max'] = 3
-        self.store['min'] = 6
-        self.store['pow'] = 7
-        slist = self.store.keys()
-        self.assertEqual('min' in slist, True)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/sqliteshelf.py b/lib/sqliteshelf.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e7f35879df1f8365b1a2770cb3db89c519d81d6
--- /dev/null
+++ b/lib/sqliteshelf.py
@@ -0,0 +1,161 @@
+"""
+by default, things are stored in a "shelf" table
+
+>>> d = SQLiteShelf("test.sdb")
+
+you can put multiple shelves into a single SQLite database
+
+>>> e = SQLiteShelf("test.sdb", "othertable")
+
+both are empty to start with
+
+>>> d
+{}
+>>> e
+{}
+
+adding stuff is as simple as a regular dict
+>>> d['a'] = "moo"
+>>> e['a'] = "moo"
+
+regular dict actions work
+
+>>> d['a']
+'moo'
+>>> e['a']
+'moo'
+>>> 'a' in d
+True
+>>> len(d)
+1
+>>> del d['a']
+>>> 'a' in d
+False
+>>> len(d)
+0
+>>> del e['a']
+
+objects can be stored in shelves
+
+>> class Test:
+..    def __init__(self):
+..        self.foo = "bar"
+..
+>> t = Test()
+>> d['t'] = t
+>> print d['t'].foo
+bar
+
+errors are as normal for a dict
+
+>>> d['x']
+Traceback (most recent call last):
+    ...
+KeyError: 'x'
+>>> del d['x']
+Traceback (most recent call last):
+    ...
+KeyError: 'x'
+
+Adding and fetching binary strings
+
+>>> d[1] = "a\\x00b"
+>>> d[1]
+'a\\x00b'
+"""
+
+try:
+    from UserDict import DictMixin
+except ImportError:
+    from collections import MutableMapping as DictMixin
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+import sys
+valtype = str
+if sys.version > '3':
+    buffer = memoryview
+    valtype = bytes
+
+import sqlite3
+
+class SQLiteDict(DictMixin):
+    def __init__(self, filename=':memory:', table='shelf', flags='r', mode=None, valtype=valtype):
+        self.table = table
+        self.valtype = valtype
+        MAKE_SHELF = 'CREATE TABLE IF NOT EXISTS '+self.table+' (key TEXT, value BLOB)'
+        MAKE_INDEX = 'CREATE UNIQUE INDEX IF NOT EXISTS '+self.table+'_keyndx ON '+self.table+'(key)'
+        self.conn = sqlite3.connect(filename)
+        self.conn.text_factory = str
+        self.conn.execute(MAKE_SHELF)
+        self.conn.execute(MAKE_INDEX)
+        self.conn.commit()
+
+    def __getitem__(self, key):
+        GET_ITEM = 'SELECT value FROM '+self.table+' WHERE key = ?'
+        item = self.conn.execute(GET_ITEM, (key,)).fetchone()
+        if item is None:
+            raise KeyError(key)
+        return self.valtype(item[0])
+
+    def __setitem__(self, key, item):
+        ADD_ITEM = 'REPLACE INTO '+self.table+' (key, value) VALUES (?,?)'
+        self.conn.execute(ADD_ITEM, (key, sqlite3.Binary(item)))
+        self.conn.commit()
+
+    def __delitem__(self, key):
+        if key not in self:
+            raise KeyError(key)
+        DEL_ITEM = 'DELETE FROM '+self.table+' WHERE key = ?'
+        self.conn.execute(DEL_ITEM, (key,))
+        self.conn.commit()
+
+    def __iter__(self):
+        c = self.conn.cursor()
+        try:
+            c.execute('SELECT key FROM '+self.table+' ORDER BY key')
+            for row in c:
+                yield row[0]
+        finally:
+            c.close()
+
+    def keys(self):
+        c = self.conn.cursor()
+        try:
+            c.execute('SELECT key FROM '+self.table+' ORDER BY key')
+            return [row[0] for row in c]
+        finally:
+            c.close()
+
+    ###################################################################
+    # optional bits
+
+    def __len__(self):
+        GET_LEN =  'SELECT COUNT(*) FROM '+self.table
+        return self.conn.execute(GET_LEN).fetchone()[0]
+
+    def close(self):
+        if self.conn is not None:
+            self.conn.commit()
+            self.conn.close()
+            self.conn = None
+
+    def __del__(self):
+        self.close()
+
+    def __repr__(self):
+        return repr(dict(self))
+
+class SQLiteShelf(SQLiteDict):
+    def __getitem__(self, key):
+        return pickle.loads(SQLiteDict.__getitem__(self, key))
+
+    def __setitem__(self, key, item):
+        SQLiteDict.__setitem__(self, key, pickle.dumps(item))
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
diff --git a/lib/trakt/__init__.py b/lib/trakt/__init__.py
index 501370aed14183e354673780d6a6e3ed2d43ce61..efdcc682c4ff680547919b7d64c9d506f8c3bb10 100644
--- a/lib/trakt/__init__.py
+++ b/lib/trakt/__init__.py
@@ -1,20 +1 @@
-import hashlib
-import requests
-
-def TraktCall(method, api, username=None, password=None, data={}):
-    base_url = 'http://api.trakt.tv/'
-
-    # if username and password given then encode password with sha1
-    auth = None
-    if username and password:
-        auth = (username, hashlib.sha1(password.encode('utf-8')).hexdigest())
-
-    # request the URL from trakt and parse the result as json
-    try:
-        resp = requests.get(base_url + method.replace("%API%", api), auth=auth, data=data).json()
-        if isinstance(resp, dict) and resp.get('status', False) == 'failure':
-            raise Exception(resp.get('error', 'Unknown Error'))
-    except:
-        return None
-
-    return resp
\ No newline at end of file
+from trakt import TraktAPI
\ No newline at end of file
diff --git a/lib/trakt/exceptions.py b/lib/trakt/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..f82e88e5c711ee5ee5544e9f54e7e2708bc86dd9
--- /dev/null
+++ b/lib/trakt/exceptions.py
@@ -0,0 +1,8 @@
+class traktException(Exception):
+    pass
+
+class traktAuthException(traktException):
+    pass
+
+class traktServerBusy(traktException):
+    pass
\ No newline at end of file
diff --git a/lib/trakt/trakt.py b/lib/trakt/trakt.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2e86c61df72bbe2f7788df1c9638ab79d4e8d91
--- /dev/null
+++ b/lib/trakt/trakt.py
@@ -0,0 +1,50 @@
+import requests
+
+from requests.auth import HTTPBasicAuth
+from exceptions import traktException, traktAuthException, traktServerBusy
+
+class TraktAPI():
+    def __init__(self, apikey, username=None, password=None, use_https=False, timeout=5):
+        self.apikey = apikey
+        self.username = username
+        self.password = password
+
+        self.protocol = 'https://' if use_https else 'http://'
+        self.timeout = timeout
+
+    def validateAccount(self):
+        return self.traktRequest("account/test/%APIKEY%")
+
+    def traktRequest(self, url, data=None):
+        base_url = self.protocol + 'api.trakt.tv/%s' % url.replace('%APIKEY%', self.apikey).replace('%USER%',
+                                                                                                    self.username)
+
+        # request the URL from trakt and parse the result as json
+        try:
+            resp = requests.get(base_url,
+                                auth=HTTPBasicAuth(self.username, self.password),
+                                data=data if data else [])
+
+            # check for http errors and raise if any are present
+            resp.raise_for_status()
+
+            # convert response to json
+            resp = resp.json()
+        except (requests.HTTPError, requests.ConnectionError) as e:
+            if e.response.status_code == 401:
+                raise traktAuthException(e)
+            elif e.response.status_code == 503:
+                raise traktServerBusy(e)
+            else:
+                raise traktException(e)
+
+        # check and confirm trakt call did not fail
+        if isinstance(resp, dict) and resp.get('status', False) == 'failure':
+            if 'message' in resp:
+                raise traktException(resp['message'])
+            if 'error' in resp:
+                raise traktException(resp['error'])
+            else:
+                raise traktException('Unknown Error')
+
+        return resp
\ No newline at end of file
diff --git a/lib/tvdb_api/MANIFEST.in b/lib/tvdb_api/MANIFEST.in
deleted file mode 100644
index bd227aa467f63c2d5039cb1d529301bbea11d7f9..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/MANIFEST.in
+++ /dev/null
@@ -1,4 +0,0 @@
-include UNLICENSE
-include readme.md
-include tests/*.py
-include Rakefile
diff --git a/lib/tvdb_api/Rakefile b/lib/tvdb_api/Rakefile
deleted file mode 100644
index 561deb7081e130d11b4204dc82e4e7f5b723f917..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/Rakefile
+++ /dev/null
@@ -1,103 +0,0 @@
-require 'fileutils'
-
-task :default => [:clean]
-
-task :clean do
-  [".", "tests"].each do |cd|
-    puts "Cleaning directory #{cd}"
-    Dir.new(cd).each do |t|
-      if t =~ /.*\.pyc$/
-        puts "Removing #{File.join(cd, t)}"
-        File.delete(File.join(cd, t))
-      end
-    end
-  end
-end
-
-desc "Upversion files"
-task :upversion do
-  puts "Upversioning"
-
-  Dir.glob("*.py").each do |filename|
-    f = File.new(filename, File::RDWR)
-    contents = f.read()
-
-    contents.gsub!(/__version__ = ".+?"/){|m|
-      cur_version = m.scan(/\d+\.\d+/)[0].to_f
-      new_version = cur_version + 0.1
-
-      puts "Current version: #{cur_version}"
-      puts "New version: #{new_version}"
-
-      new_line = "__version__ = \"#{new_version}\""
-
-      puts "Old line: #{m}"
-      puts "New line: #{new_line}"
-
-      m = new_line
-    }
-
-    puts contents[0]
-
-    f.truncate(0) # empty the existing file
-    f.seek(0)
-    f.write(contents.to_s) # write modified file
-    f.close()
-  end
-end
-
-desc "Upload current version to PyPi"
-task :topypi => :test do
-  cur_file = File.open("tvdb_api.py").read()
-  tvdb_api_version = cur_file.scan(/__version__ = "(.*)"/)
-  tvdb_api_version = tvdb_api_version[0][0].to_f
-
-  puts "Build sdist and send tvdb_api v#{tvdb_api_version} to PyPi?"
-  if $stdin.gets.chomp == "y"
-    puts "Sending source-dist (sdist) to PyPi"
-
-    if system("python setup.py sdist register upload")
-      puts "tvdb_api uploaded!"
-    end
-
-  else
-    puts "Cancelled"
-  end
-end
-
-desc "Profile by running unittests"
-task :profile do
-  cd "tests"
-  puts "Profiling.."
-  `python -m cProfile -o prof_runtest.prof runtests.py`
-  puts "Converting prof to dot"
-  `python gprof2dot.py -o prof_runtest.dot -f pstats prof_runtest.prof`
-  puts "Generating graph"
-  `~/Applications/dev/graphviz.app/Contents/macOS/dot -Tpng -o profile.png prof_runtest.dot -Gbgcolor=black`
-  puts "Cleanup"
-  rm "prof_runtest.dot"
-  rm "prof_runtest.prof"
-end
-
-task :test do
-  puts "Nosetest'ing"
-  if not system("nosetests -v --with-doctest")
-    raise "Test failed!"
-  end
-
-  puts "Doctesting *.py (excluding setup.py)"
-  Dir.glob("*.py").select{|e| ! e.match(/setup.py/)}.each do |filename|
-    if filename =~ /^setup\.py/
-      skip
-    end
-    puts "Doctesting #{filename}"
-    if not system("python", "-m", "doctest", filename)
-      raise "Failed doctest"
-    end
-  end
-
-  puts "Doctesting readme.md"
-  if not system("python", "-m", "doctest", "readme.md")
-    raise "Doctest"
-  end
-end
diff --git a/lib/tvdb_api/UNLICENSE b/lib/tvdb_api/UNLICENSE
deleted file mode 100644
index c4205d41d02151282b29c65cd518438b457229dd..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/UNLICENSE
+++ /dev/null
@@ -1,26 +0,0 @@
-Copyright 2011-2012 Ben Dickson (dbr)
-
-This is free and unencumbered software released into the public domain.
-
-Anyone is free to copy, modify, publish, use, compile, sell, or
-distribute this software, either in source code form or as a compiled
-binary, for any purpose, commercial or non-commercial, and by any
-means.
-
-In jurisdictions that recognize copyright laws, the author or authors
-of this software dedicate any and all copyright interest in the
-software to the public domain. We make this dedication for the benefit
-of the public at large and to the detriment of our heirs and
-successors. We intend this dedication to be an overt act of
-relinquishment in perpetuity of all present and future rights to this
-software under copyright law.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
-
-For more information, please refer to <http://unlicense.org/>
diff --git a/lib/tvdb_api/readme.md b/lib/tvdb_api/readme.md
deleted file mode 100644
index b0b0cfdf61b9f5390a49ac527d80935d160207d4..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/readme.md
+++ /dev/null
@@ -1,109 +0,0 @@
-# `tvdb_api`
-
-`tvdb_api` is an easy to use interface to [thetvdb.com][tvdb]
-
-`tvnamer` has moved to a separate repository: [github.com/dbr/tvnamer][tvnamer] - it is a utility which uses `tvdb_api` to rename files from `some.show.s01e03.blah.abc.avi` to `Some Show - [01x03] - The Episode Name.avi` (which works by getting the episode name from `tvdb_api`)
-
-[![Build Status](https://secure.travis-ci.org/dbr/tvdb_api.png?branch=master)](http://travis-ci.org/dbr/tvdb_api)
-
-## To install
-
-You can easily install `tvdb_api` via `easy_install`
-
-    easy_install tvdb_api
-
-You may need to use sudo, depending on your setup:
-
-    sudo easy_install tvdb_api
-
-The [`tvnamer`][tvnamer] command-line tool can also be installed via `easy_install`, this installs `tvdb_api` as a dependancy:
-
-    easy_install tvnamer
-
-
-## Basic usage
-
-    import tvdb_api
-    t = indexerApi()
-    episode = t['My Name Is Earl'][1][3] # get season 1, episode 3 of show
-    print episode['episodename'] # Print episode name
-
-## Advanced usage
-
-Most of the documentation is in docstrings. The examples are tested (using doctest) so will always be up to date and working.
-
-The docstring for `Tvdb.__init__` lists all initialisation arguments, including support for non-English searches, custom "Select Series" interfaces and enabling the retrieval of banners and extended actor information. You can also override the default API key using `apikey`, recommended if you're using `tvdb_api` in a larger script or application
-
-### Exceptions
-
-There are several exceptions you may catch, these can be imported from `tvdb_api`:
-
-- `tvdb_error` - this is raised when there is an error communicating with [thetvdb.com][tvdb] (a network error most commonly)
-- `tvdb_userabort` - raised when a user aborts the Select Series dialog (by `ctrl+c`, or entering `q`)
-- `tvdb_shownotfound` - raised when `t['show name']` cannot find anything
-- `tvdb_seasonnotfound` - raised when the requested series (`t['show name][99]`) does not exist
-- `tvdb_episodenotfound` - raised when the requested episode (`t['show name][1][99]`) does not exist.
-- `tvdb_attributenotfound` - raised when the requested attribute is not found (`t['show name']['an attribute']`, `t['show name'][1]['an attribute']`, or ``t['show name'][1][1]['an attribute']``)
-
-### Series data
-
-All data exposed by [thetvdb.com][tvdb] is accessible via the `Show` class. A Show is retrieved by doing..
-
-    >>> import tvdb_api
-    >>> t = indexerApi()
-    >>> show = t['scrubs']
-    >>> type(show)
-    <class 'tvdb_api.Show'>
-
-For example, to find out what network Scrubs is aired:
-
-    >>> t['scrubs']['network']
-    u'ABC'
-
-The data is stored in an attribute named `data`, within the Show instance:
-
-    >>> t['scrubs'].data.keys()
-    ['networkid', 'rating', 'airs_dayofweek', 'contentrating', 'seriesname', 'id', 'airs_time', 'network', 'fanart', 'lastupdated', 'actors', 'ratingcount', 'status', 'added', 'poster', 'imdb_id', 'genre', 'banner', 'seriesid', 'language', 'zap2it_id', 'addedby', 'tms_wanted', 'firstaired', 'runtime', 'overview']
-
-Although each element is also accessible via `t['scrubs']` for ease-of-use:
-
-    >>> t['scrubs']['rating']
-    u'9.0'
-
-This is the recommended way of retrieving "one-off" data (for example, if you are only interested in "seriesname"). If you wish to iterate over all data, or check if a particular show has a specific piece of data, use the `data` attribute,
-
-    >>> 'rating' in t['scrubs'].data
-    True
-
-### Banners and actors
-
-Since banners and actors are separate XML files, retrieving them by default is undesirable. If you wish to retrieve banners (and other fanart), use the `banners` Tvdb initialisation argument:
-
-    >>> from tvdb_api import Tvdb
-    >>> t = Tvdb(banners = True)
-
-Then access the data using a `Show`'s `_banner` key:
-
-    >>> t['scrubs']['_banners'].keys()
-    ['fanart', 'poster', 'series', 'season']
-
-The banner data structure will be improved in future versions.
-
-Extended actor data is accessible similarly:
-
-    >>> t = Tvdb(actors = True)
-    >>> actors = t['scrubs']['_actors']
-    >>> actors[0]
-    <Actor "Zach Braff">
-    >>> actors[0].keys()
-    ['sortorder', 'image', 'role', 'id', 'name']
-    >>> actors[0]['role']
-    u'Dr. John Michael "J.D." Dorian'
-
-Remember a simple list of actors is accessible via the default Show data:
-
-    >>> t['scrubs']['actors']
-    u'|Zach Braff|Donald Faison|Sarah Chalke|Christa Miller|Aloma Wright|Robert Maschio|Sam Lloyd|Neil Flynn|Ken Jenkins|Judy Reyes|John C. McGinley|Travis Schuldt|Johnny Kastl|Heather Graham|Michael Mosley|Kerry Bish\xe9|Dave Franco|Eliza Coupe|'
-
-[tvdb]: http://thetvdb.com
-[tvnamer]: http://github.com/dbr/tvnamer
diff --git a/lib/tvdb_api/setup.py b/lib/tvdb_api/setup.py
deleted file mode 100644
index e4d66801e3b2235810585a2dcdce980661d5faac..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/setup.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from setuptools import setup
-setup(
-name = 'tvdb_api',
-version='1.9',
-
-author='dbr/Ben',
-description='Interface to thetvdb.com',
-url='http://github.com/dbr/tvdb_api/tree/master',
-license='unlicense',
-
-long_description="""\
-An easy to use API interface to TheTVDB.com
-Basic usage is:
-
->>> import tvdb_api
->>> t = tvdb_api.Tvdb()
->>> ep = t['My Name Is Earl'][1][22]
->>> ep
-<Episode 01x22 - Stole a Badge>
->>> ep['episodename']
-u'Stole a Badge'
-""",
-
-py_modules = ['tvdb_api', 'tvdb_ui', 'tvdb_exceptions', 'tvdb_cache'],
-
-classifiers=[
-    "Intended Audience :: Developers",
-    "Natural Language :: English",
-    "Operating System :: OS Independent",
-    "Programming Language :: Python",
-    "Topic :: Multimedia",
-    "Topic :: Utilities",
-    "Topic :: Software Development :: Libraries :: Python Modules",
-]
-)
diff --git a/lib/tvdb_api/tests/gprof2dot.py b/lib/tvdb_api/tests/gprof2dot.py
deleted file mode 100644
index 20a5a50aaa07996367d3be2c3650136e91c52a25..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/tests/gprof2dot.py
+++ /dev/null
@@ -1,1638 +0,0 @@
-#!/usr/bin/env python2
-#
-# Copyright 2008 Jose Fonseca
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program 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 Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-
-"""Generate a dot graph from the output of several profilers."""
-
-__author__ = "Jose Fonseca"
-
-__version__ = "1.0"
-
-
-import sys
-import math
-import os.path
-import re
-import textwrap
-import optparse
-
-
-try:
-    # Debugging helper module
-    import debug
-except ImportError:
-    pass
-
-
-def percentage(p):
-    return "%.02f%%" % (p*100.0,)
-
-def add(a, b):
-    return a + b
-
-def equal(a, b):
-    if a == b:
-        return a
-    else:
-        return None
-
-def fail(a, b):
-    assert False
-
-
-def ratio(numerator, denominator):
-    numerator = float(numerator)
-    denominator = float(denominator)
-    assert 0.0 <= numerator
-    assert numerator <= denominator
-    try:
-        return numerator/denominator
-    except ZeroDivisionError:
-        # 0/0 is undefined, but 1.0 yields more useful results
-        return 1.0
-
-
-class UndefinedEvent(Exception):
-    """Raised when attempting to get an event which is undefined."""
-    
-    def __init__(self, event):
-        Exception.__init__(self)
-        self.event = event
-
-    def __str__(self):
-        return 'unspecified event %s' % self.event.name
-
-
-class Event(object):
-    """Describe a kind of event, and its basic operations."""
-
-    def __init__(self, name, null, aggregator, formatter = str):
-        self.name = name
-        self._null = null
-        self._aggregator = aggregator
-        self._formatter = formatter
-
-    def __eq__(self, other):
-        return self is other
-
-    def __hash__(self):
-        return id(self)
-
-    def null(self):
-        return self._null
-
-    def aggregate(self, val1, val2):
-        """Aggregate two event values."""
-        assert val1 is not None
-        assert val2 is not None
-        return self._aggregator(val1, val2)
-    
-    def format(self, val):
-        """Format an event value."""
-        assert val is not None
-        return self._formatter(val)
-
-
-MODULE = Event("Module", None, equal)
-PROCESS = Event("Process", None, equal)
-
-CALLS = Event("Calls", 0, add)
-SAMPLES = Event("Samples", 0, add)
-
-TIME = Event("Time", 0.0, add, lambda x: '(' + str(x) + ')')
-TIME_RATIO = Event("Time ratio", 0.0, add, lambda x: '(' + percentage(x) + ')')
-TOTAL_TIME = Event("Total time", 0.0, fail)
-TOTAL_TIME_RATIO = Event("Total time ratio", 0.0, fail, percentage)
-
-CALL_RATIO = Event("Call ratio", 0.0, add, percentage)
-
-PRUNE_RATIO = Event("Prune ratio", 0.0, add, percentage)
-
-
-class Object(object):
-    """Base class for all objects in profile which can store events."""
-
-    def __init__(self, events=None):
-        if events is None:
-            self.events = {}
-        else:
-            self.events = events
-
-    def __hash__(self):
-        return id(self)
-
-    def __eq__(self, other):
-        return self is other
-
-    def __contains__(self, event):
-        return event in self.events
-    
-    def __getitem__(self, event):
-        try:
-            return self.events[event]
-        except KeyError:
-            raise UndefinedEvent(event)
-    
-    def __setitem__(self, event, value):
-        if value is None:
-            if event in self.events:
-                del self.events[event]
-        else:
-            self.events[event] = value
-
-
-class Call(Object):
-    """A call between functions.
-    
-    There should be at most one call object for every pair of functions.
-    """
-
-    def __init__(self, callee_id):
-        Object.__init__(self)
-        self.callee_id = callee_id    
-
-
-class Function(Object):
-    """A function."""
-
-    def __init__(self, id, name):
-        Object.__init__(self)
-        self.id = id
-        self.name = name
-        self.calls = {}
-        self.cycle = None
-    
-    def add_call(self, call):
-        if call.callee_id in self.calls:
-            sys.stderr.write('warning: overwriting call from function %s to %s\n' % (str(self.id), str(call.callee_id)))
-        self.calls[call.callee_id] = call
-
-    # TODO: write utility functions
-
-    def __repr__(self):
-        return self.name
-
-
-class Cycle(Object):
-    """A cycle made from recursive function calls."""
-
-    def __init__(self):
-        Object.__init__(self)
-        # XXX: Do cycles need an id?
-        self.functions = set()
-
-    def add_function(self, function):
-        assert function not in self.functions
-        self.functions.add(function)
-        # XXX: Aggregate events?
-        if function.cycle is not None:
-            for other in function.cycle.functions:
-                if function not in self.functions:
-                    self.add_function(other)
-        function.cycle = self
-
-
-class Profile(Object):
-    """The whole profile."""
-
-    def __init__(self):
-        Object.__init__(self)
-        self.functions = {}
-        self.cycles = []
-
-    def add_function(self, function):
-        if function.id in self.functions:
-            sys.stderr.write('warning: overwriting function %s (id %s)\n' % (function.name, str(function.id)))
-        self.functions[function.id] = function
-
-    def add_cycle(self, cycle):
-        self.cycles.append(cycle)
-
-    def validate(self):
-        """Validate the edges."""
-
-        for function in self.functions.itervalues():
-            for callee_id in function.calls.keys():
-                assert function.calls[callee_id].callee_id == callee_id
-                if callee_id not in self.functions:
-                    sys.stderr.write('warning: call to undefined function %s from function %s\n' % (str(callee_id), function.name))
-                    del function.calls[callee_id]
-
-    def find_cycles(self):
-        """Find cycles using Tarjan's strongly connected components algorithm."""
-
-        # Apply the Tarjan's algorithm successively until all functions are visited
-        visited = set()
-        for function in self.functions.itervalues():
-            if function not in visited:
-                self._tarjan(function, 0, [], {}, {}, visited)
-        cycles = []
-        for function in self.functions.itervalues():
-            if function.cycle is not None and function.cycle not in cycles:
-                cycles.append(function.cycle)
-        self.cycles = cycles
-        if 0:
-            for cycle in cycles:
-                sys.stderr.write("Cycle:\n")
-                for member in cycle.functions:
-                    sys.stderr.write("\t%s\n" % member.name)
-    
-    def _tarjan(self, function, order, stack, orders, lowlinks, visited):
-        """Tarjan's strongly connected components algorithm.
-
-        See also:
-        - http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
-        """
-
-        visited.add(function)
-        orders[function] = order
-        lowlinks[function] = order
-        order += 1
-        pos = len(stack)
-        stack.append(function)
-        for call in function.calls.itervalues():
-            callee = self.functions[call.callee_id]
-            # TODO: use a set to optimize lookup
-            if callee not in orders:
-                order = self._tarjan(callee, order, stack, orders, lowlinks, visited)
-                lowlinks[function] = min(lowlinks[function], lowlinks[callee])
-            elif callee in stack:
-                lowlinks[function] = min(lowlinks[function], orders[callee])
-        if lowlinks[function] == orders[function]:
-            # Strongly connected component found
-            members = stack[pos:]
-            del stack[pos:]
-            if len(members) > 1:
-                cycle = Cycle()
-                for member in members:
-                    cycle.add_function(member)
-        return order
-
-    def call_ratios(self, event):
-        # Aggregate for incoming calls
-        cycle_totals = {}
-        for cycle in self.cycles:
-            cycle_totals[cycle] = 0.0
-        function_totals = {}
-        for function in self.functions.itervalues():
-            function_totals[function] = 0.0
-        for function in self.functions.itervalues():
-            for call in function.calls.itervalues():
-                if call.callee_id != function.id:
-                    callee = self.functions[call.callee_id]
-                    function_totals[callee] += call[event]
-                    if callee.cycle is not None and callee.cycle is not function.cycle:
-                        cycle_totals[callee.cycle] += call[event]
-
-        # Compute the ratios
-        for function in self.functions.itervalues():
-            for call in function.calls.itervalues():
-                assert CALL_RATIO not in call
-                if call.callee_id != function.id:
-                    callee = self.functions[call.callee_id]
-                    if callee.cycle is not None and callee.cycle is not function.cycle:
-                        total = cycle_totals[callee.cycle]
-                    else:
-                        total = function_totals[callee]
-                    call[CALL_RATIO] = ratio(call[event], total)
-
-    def integrate(self, outevent, inevent):
-        """Propagate function time ratio allong the function calls.
-
-        Must be called after finding the cycles.
-
-        See also:
-        - http://citeseer.ist.psu.edu/graham82gprof.html
-        """
-
-        # Sanity checking
-        assert outevent not in self
-        for function in self.functions.itervalues():
-            assert outevent not in function
-            assert inevent in function
-            for call in function.calls.itervalues():
-                assert outevent not in call
-                if call.callee_id != function.id:
-                    assert CALL_RATIO in call
-
-        # Aggregate the input for each cycle 
-        for cycle in self.cycles:
-            total = inevent.null()
-            for function in self.functions.itervalues():
-                total = inevent.aggregate(total, function[inevent])
-            self[inevent] = total
-
-        # Integrate along the edges
-        total = inevent.null()
-        for function in self.functions.itervalues():
-            total = inevent.aggregate(total, function[inevent])
-            self._integrate_function(function, outevent, inevent)
-        self[outevent] = total
-
-    def _integrate_function(self, function, outevent, inevent):
-        if function.cycle is not None:
-            return self._integrate_cycle(function.cycle, outevent, inevent)
-        else:
-            if outevent not in function:
-                total = function[inevent]
-                for call in function.calls.itervalues():
-                    if call.callee_id != function.id:
-                        total += self._integrate_call(call, outevent, inevent)
-                function[outevent] = total
-            return function[outevent]
-    
-    def _integrate_call(self, call, outevent, inevent):
-        assert outevent not in call
-        assert CALL_RATIO in call
-        callee = self.functions[call.callee_id]
-        subtotal = call[CALL_RATIO]*self._integrate_function(callee, outevent, inevent)
-        call[outevent] = subtotal
-        return subtotal
-
-    def _integrate_cycle(self, cycle, outevent, inevent):
-        if outevent not in cycle:
-
-            total = inevent.null()
-            for member in cycle.functions:
-                subtotal = member[inevent]
-                for call in member.calls.itervalues():
-                    callee = self.functions[call.callee_id]
-                    if callee.cycle is not cycle:
-                        subtotal += self._integrate_call(call, outevent, inevent)
-                total += subtotal
-            cycle[outevent] = total
-            
-            callees = {}
-            for function in self.functions.itervalues():
-                if function.cycle is not cycle:
-                    for call in function.calls.itervalues():
-                        callee = self.functions[call.callee_id]
-                        if callee.cycle is cycle:
-                            try:
-                                callees[callee] += call[CALL_RATIO]
-                            except KeyError:
-                                callees[callee] = call[CALL_RATIO]
-
-            for callee, call_ratio in callees.iteritems():
-                ranks = {}
-                call_ratios = {}
-                partials = {}
-                self._rank_cycle_function(cycle, callee, 0, ranks)
-                self._call_ratios_cycle(cycle, callee, ranks, call_ratios, set())
-                partial = self._integrate_cycle_function(cycle, callee, call_ratio, partials, ranks, call_ratios, outevent, inevent)
-                assert partial == max(partials.values())
-                assert not total or abs(1.0 - partial/(call_ratio*total)) <= 0.001
-            
-        return cycle[outevent]
-
-    def _rank_cycle_function(self, cycle, function, rank, ranks):
-        if function not in ranks or ranks[function] > rank:
-            ranks[function] = rank
-            for call in function.calls.itervalues():
-                if call.callee_id != function.id:
-                    callee = self.functions[call.callee_id]
-                    if callee.cycle is cycle:
-                        self._rank_cycle_function(cycle, callee, rank + 1, ranks)
-
-    def _call_ratios_cycle(self, cycle, function, ranks, call_ratios, visited):
-        if function not in visited:
-            visited.add(function)
-            for call in function.calls.itervalues():
-                if call.callee_id != function.id:
-                    callee = self.functions[call.callee_id]
-                    if callee.cycle is cycle:
-                        if ranks[callee] > ranks[function]:
-                            call_ratios[callee] = call_ratios.get(callee, 0.0) + call[CALL_RATIO]
-                            self._call_ratios_cycle(cycle, callee, ranks, call_ratios, visited)
-
-    def _integrate_cycle_function(self, cycle, function, partial_ratio, partials, ranks, call_ratios, outevent, inevent):
-        if function not in partials:
-            partial = partial_ratio*function[inevent]
-            for call in function.calls.itervalues():
-                if call.callee_id != function.id:
-                    callee = self.functions[call.callee_id]
-                    if callee.cycle is not cycle:
-                        assert outevent in call
-                        partial += partial_ratio*call[outevent]
-                    else:
-                        if ranks[callee] > ranks[function]:
-                            callee_partial = self._integrate_cycle_function(cycle, callee, partial_ratio, partials, ranks, call_ratios, outevent, inevent)
-                            call_ratio = ratio(call[CALL_RATIO], call_ratios[callee])
-                            call_partial = call_ratio*callee_partial
-                            try:
-                                call[outevent] += call_partial
-                            except UndefinedEvent:
-                                call[outevent] = call_partial
-                            partial += call_partial
-            partials[function] = partial
-            try:
-                function[outevent] += partial
-            except UndefinedEvent:
-                function[outevent] = partial
-        return partials[function]
-
-    def aggregate(self, event):
-        """Aggregate an event for the whole profile."""
-
-        total = event.null()
-        for function in self.functions.itervalues():
-            try:
-                total = event.aggregate(total, function[event])
-            except UndefinedEvent:
-                return
-        self[event] = total
-
-    def ratio(self, outevent, inevent):
-        assert outevent not in self
-        assert inevent in self
-        for function in self.functions.itervalues():
-            assert outevent not in function
-            assert inevent in function
-            function[outevent] = ratio(function[inevent], self[inevent])
-            for call in function.calls.itervalues():
-                assert outevent not in call
-                if inevent in call:
-                    call[outevent] = ratio(call[inevent], self[inevent])
-        self[outevent] = 1.0
-
-    def prune(self, node_thres, edge_thres):
-        """Prune the profile"""
-
-        # compute the prune ratios
-        for function in self.functions.itervalues():
-            try:
-                function[PRUNE_RATIO] = function[TOTAL_TIME_RATIO]
-            except UndefinedEvent:
-                pass
-
-            for call in function.calls.itervalues():
-                callee = self.functions[call.callee_id]
-
-                if TOTAL_TIME_RATIO in call:
-                    # handle exact cases first
-                    call[PRUNE_RATIO] = call[TOTAL_TIME_RATIO] 
-                else:
-                    try:
-                        # make a safe estimate
-                        call[PRUNE_RATIO] = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO]) 
-                    except UndefinedEvent:
-                        pass
-
-        # prune the nodes
-        for function_id in self.functions.keys():
-            function = self.functions[function_id]
-            try:
-                if function[PRUNE_RATIO] < node_thres:
-                    del self.functions[function_id]
-            except UndefinedEvent:
-                pass
-
-        # prune the egdes
-        for function in self.functions.itervalues():
-            for callee_id in function.calls.keys():
-                call = function.calls[callee_id]
-                try:
-                    if callee_id not in self.functions or call[PRUNE_RATIO] < edge_thres:
-                        del function.calls[callee_id]
-                except UndefinedEvent:
-                    pass
-    
-    def dump(self):
-        for function in self.functions.itervalues():
-            sys.stderr.write('Function %s:\n' % (function.name,))
-            self._dump_events(function.events)
-            for call in function.calls.itervalues():
-                callee = self.functions[call.callee_id]
-                sys.stderr.write('  Call %s:\n' % (callee.name,))
-                self._dump_events(call.events)
-
-    def _dump_events(self, events):
-        for event, value in events.iteritems():
-            sys.stderr.write('    %s: %s\n' % (event.name, event.format(value)))
-
-
-class Struct:
-    """Masquerade a dictionary with a structure-like behavior."""
-
-    def __init__(self, attrs = None):
-        if attrs is None:
-            attrs = {}
-        self.__dict__['_attrs'] = attrs
-    
-    def __getattr__(self, name):
-        try:
-            return self._attrs[name]
-        except KeyError:
-            raise AttributeError(name)
-
-    def __setattr__(self, name, value):
-        self._attrs[name] = value
-
-    def __str__(self):
-        return str(self._attrs)
-
-    def __repr__(self):
-        return repr(self._attrs)
-    
-
-class ParseError(Exception):
-    """Raised when parsing to signal mismatches."""
-
-    def __init__(self, msg, line):
-        self.msg = msg
-        # TODO: store more source line information
-        self.line = line
-
-    def __str__(self):
-        return '%s: %r' % (self.msg, self.line)
-
-
-class Parser:
-    """Parser interface."""
-
-    def __init__(self):
-        pass
-
-    def parse(self):
-        raise NotImplementedError
-
-    
-class LineParser(Parser):
-    """Base class for parsers that read line-based formats."""
-
-    def __init__(self, file):
-        Parser.__init__(self)
-        self._file = file
-        self.__line = None
-        self.__eof = False
-
-    def readline(self):
-        line = self._file.readline()
-        if not line:
-            self.__line = ''
-            self.__eof = True
-        self.__line = line.rstrip('\r\n')
-
-    def lookahead(self):
-        assert self.__line is not None
-        return self.__line
-
-    def consume(self):
-        assert self.__line is not None
-        line = self.__line
-        self.readline()
-        return line
-
-    def eof(self):
-        assert self.__line is not None
-        return self.__eof
-
-
-class GprofParser(Parser):
-    """Parser for GNU gprof output.
-
-    See also:
-    - Chapter "Interpreting gprof's Output" from the GNU gprof manual
-      http://sourceware.org/binutils/docs-2.18/gprof/Call-Graph.html#Call-Graph
-    - File "cg_print.c" from the GNU gprof source code
-      http://sourceware.org/cgi-bin/cvsweb.cgi/~checkout~/src/gprof/cg_print.c?rev=1.12&cvsroot=src
-    """
-
-    def __init__(self, fp):
-        Parser.__init__(self)
-        self.fp = fp
-        self.functions = {}
-        self.cycles = {}
-
-    def readline(self):
-        line = self.fp.readline()
-        if not line:
-            sys.stderr.write('error: unexpected end of file\n')
-            sys.exit(1)
-        line = line.rstrip('\r\n')
-        return line
-
-    _int_re = re.compile(r'^\d+$')
-    _float_re = re.compile(r'^\d+\.\d+$')
-
-    def translate(self, mo):
-        """Extract a structure from a match object, while translating the types in the process."""
-        attrs = {}
-        groupdict = mo.groupdict()
-        for name, value in groupdict.iteritems():
-            if value is None:
-                value = None
-            elif self._int_re.match(value):
-                value = int(value)
-            elif self._float_re.match(value):
-                value = float(value)
-            attrs[name] = (value)
-        return Struct(attrs)
-
-    _cg_header_re = re.compile(
-        # original gprof header
-        r'^\s+called/total\s+parents\s*$|' +
-        r'^index\s+%time\s+self\s+descendents\s+called\+self\s+name\s+index\s*$|' +
-        r'^\s+called/total\s+children\s*$|' +
-        # GNU gprof header
-        r'^index\s+%\s+time\s+self\s+children\s+called\s+name\s*$'
-    )
-
-    _cg_ignore_re = re.compile(
-        # spontaneous
-        r'^\s+<spontaneous>\s*$|'
-        # internal calls (such as "mcount")
-        r'^.*\((\d+)\)$'
-    )
-
-    _cg_primary_re = re.compile(
-        r'^\[(?P<index>\d+)\]' + 
-        r'\s+(?P<percentage_time>\d+\.\d+)' + 
-        r'\s+(?P<self>\d+\.\d+)' + 
-        r'\s+(?P<descendants>\d+\.\d+)' + 
-        r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' + 
-        r'\s+(?P<name>\S.*?)' +
-        r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
-        r'\s\[(\d+)\]$'
-    )
-
-    _cg_parent_re = re.compile(
-        r'^\s+(?P<self>\d+\.\d+)?' + 
-        r'\s+(?P<descendants>\d+\.\d+)?' + 
-        r'\s+(?P<called>\d+)(?:/(?P<called_total>\d+))?' + 
-        r'\s+(?P<name>\S.*?)' +
-        r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
-        r'\s\[(?P<index>\d+)\]$'
-    )
-
-    _cg_child_re = _cg_parent_re
-
-    _cg_cycle_header_re = re.compile(
-        r'^\[(?P<index>\d+)\]' + 
-        r'\s+(?P<percentage_time>\d+\.\d+)' + 
-        r'\s+(?P<self>\d+\.\d+)' + 
-        r'\s+(?P<descendants>\d+\.\d+)' + 
-        r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' + 
-        r'\s+<cycle\s(?P<cycle>\d+)\sas\sa\swhole>' +
-        r'\s\[(\d+)\]$'
-    )
-
-    _cg_cycle_member_re = re.compile(
-        r'^\s+(?P<self>\d+\.\d+)?' + 
-        r'\s+(?P<descendants>\d+\.\d+)?' + 
-        r'\s+(?P<called>\d+)(?:\+(?P<called_self>\d+))?' + 
-        r'\s+(?P<name>\S.*?)' +
-        r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
-        r'\s\[(?P<index>\d+)\]$'
-    )
-
-    _cg_sep_re = re.compile(r'^--+$')
-
-    def parse_function_entry(self, lines):
-        parents = []
-        children = []
-
-        while True:
-            if not lines:
-                sys.stderr.write('warning: unexpected end of entry\n')
-            line = lines.pop(0)
-            if line.startswith('['):
-                break
-        
-            # read function parent line
-            mo = self._cg_parent_re.match(line)
-            if not mo:
-                if self._cg_ignore_re.match(line):
-                    continue
-                sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
-            else:
-                parent = self.translate(mo)
-                parents.append(parent)
-
-        # read primary line
-        mo = self._cg_primary_re.match(line)
-        if not mo:
-            sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
-            return
-        else:
-            function = self.translate(mo)
-
-        while lines:
-            line = lines.pop(0)
-            
-            # read function subroutine line
-            mo = self._cg_child_re.match(line)
-            if not mo:
-                if self._cg_ignore_re.match(line):
-                    continue
-                sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
-            else:
-                child = self.translate(mo)
-                children.append(child)
-        
-        function.parents = parents
-        function.children = children
-
-        self.functions[function.index] = function
-
-    def parse_cycle_entry(self, lines):
-
-        # read cycle header line
-        line = lines[0]
-        mo = self._cg_cycle_header_re.match(line)
-        if not mo:
-            sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
-            return
-        cycle = self.translate(mo)
-
-        # read cycle member lines
-        cycle.functions = []
-        for line in lines[1:]:
-            mo = self._cg_cycle_member_re.match(line)
-            if not mo:
-                sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
-                continue
-            call = self.translate(mo)
-            cycle.functions.append(call)
-        
-        self.cycles[cycle.cycle] = cycle
-
-    def parse_cg_entry(self, lines):
-        if lines[0].startswith("["):
-            self.parse_cycle_entry(lines)
-        else:
-            self.parse_function_entry(lines)
-
-    def parse_cg(self):
-        """Parse the call graph."""
-
-        # skip call graph header
-        while not self._cg_header_re.match(self.readline()):
-            pass
-        line = self.readline()
-        while self._cg_header_re.match(line):
-            line = self.readline()
-
-        # process call graph entries
-        entry_lines = []
-        while line != '\014': # form feed
-            if line and not line.isspace():
-                if self._cg_sep_re.match(line):
-                    self.parse_cg_entry(entry_lines)
-                    entry_lines = []
-                else:
-                    entry_lines.append(line)            
-            line = self.readline()
-    
-    def parse(self):
-        self.parse_cg()
-        self.fp.close()
-
-        profile = Profile()
-        profile[TIME] = 0.0
-        
-        cycles = {}
-        for index in self.cycles.iterkeys():
-            cycles[index] = Cycle()
-
-        for entry in self.functions.itervalues():
-            # populate the function
-            function = Function(entry.index, entry.name)
-            function[TIME] = entry.self
-            if entry.called is not None:
-                function[CALLS] = entry.called
-            if entry.called_self is not None:
-                call = Call(entry.index)
-                call[CALLS] = entry.called_self
-                function[CALLS] += entry.called_self
-            
-            # populate the function calls
-            for child in entry.children:
-                call = Call(child.index)
-                
-                assert child.called is not None
-                call[CALLS] = child.called
-
-                if child.index not in self.functions:
-                    # NOTE: functions that were never called but were discovered by gprof's 
-                    # static call graph analysis dont have a call graph entry so we need
-                    # to add them here
-                    missing = Function(child.index, child.name)
-                    function[TIME] = 0.0
-                    function[CALLS] = 0
-                    profile.add_function(missing)
-
-                function.add_call(call)
-
-            profile.add_function(function)
-
-            if entry.cycle is not None:
-                cycles[entry.cycle].add_function(function)
-
-            profile[TIME] = profile[TIME] + function[TIME]
-
-        for cycle in cycles.itervalues():
-            profile.add_cycle(cycle)
-
-        # Compute derived events
-        profile.validate()
-        profile.ratio(TIME_RATIO, TIME)
-        profile.call_ratios(CALLS)
-        profile.integrate(TOTAL_TIME, TIME)
-        profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
-
-        return profile
-
-
-class OprofileParser(LineParser):
-    """Parser for oprofile callgraph output.
-    
-    See also:
-    - http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph
-    """
-
-    _fields_re = {
-        'samples': r'(?P<samples>\d+)',
-        '%': r'(?P<percentage>\S+)',
-        'linenr info': r'(?P<source>\(no location information\)|\S+:\d+)',
-        'image name': r'(?P<image>\S+(?:\s\(tgid:[^)]*\))?)',
-        'app name': r'(?P<application>\S+)',
-        'symbol name': r'(?P<symbol>\(no symbols\)|.+?)',
-    }
-
-    def __init__(self, infile):
-        LineParser.__init__(self, infile)
-        self.entries = {}
-        self.entry_re = None
-
-    def add_entry(self, callers, function, callees):
-        try:
-            entry = self.entries[function.id]
-        except KeyError:
-            self.entries[function.id] = (callers, function, callees)
-        else:
-            callers_total, function_total, callees_total = entry
-            self.update_subentries_dict(callers_total, callers)
-            function_total.samples += function.samples
-            self.update_subentries_dict(callees_total, callees)
-    
-    def update_subentries_dict(self, totals, partials):
-        for partial in partials.itervalues():
-            try:
-                total = totals[partial.id]
-            except KeyError:
-                totals[partial.id] = partial
-            else:
-                total.samples += partial.samples
-        
-    def parse(self):
-        # read lookahead
-        self.readline()
-
-        self.parse_header()
-        while self.lookahead():
-            self.parse_entry()
-
-        profile = Profile()
-
-        reverse_call_samples = {}
-        
-        # populate the profile
-        profile[SAMPLES] = 0
-        for _callers, _function, _callees in self.entries.itervalues():
-            function = Function(_function.id, _function.name)
-            function[SAMPLES] = _function.samples
-            profile.add_function(function)
-            profile[SAMPLES] += _function.samples
-
-            if _function.application:
-                function[PROCESS] = os.path.basename(_function.application)
-            if _function.image:
-                function[MODULE] = os.path.basename(_function.image)
-
-            total_callee_samples = 0
-            for _callee in _callees.itervalues():
-                total_callee_samples += _callee.samples
-
-            for _callee in _callees.itervalues():
-                if not _callee.self:
-                    call = Call(_callee.id)
-                    call[SAMPLES] = _callee.samples
-                    function.add_call(call)
-                
-        # compute derived data
-        profile.validate()
-        profile.find_cycles()
-        profile.ratio(TIME_RATIO, SAMPLES)
-        profile.call_ratios(SAMPLES)
-        profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
-        return profile
-
-    def parse_header(self):
-        while not self.match_header():
-            self.consume()
-        line = self.lookahead()
-        fields = re.split(r'\s\s+', line)
-        entry_re = r'^\s*' + r'\s+'.join([self._fields_re[field] for field in fields]) + r'(?P<self>\s+\[self\])?$'
-        self.entry_re = re.compile(entry_re)
-        self.skip_separator()
-
-    def parse_entry(self):
-        callers = self.parse_subentries()
-        if self.match_primary():
-            function = self.parse_subentry()
-            if function is not None:
-                callees = self.parse_subentries()
-                self.add_entry(callers, function, callees)
-        self.skip_separator()
-
-    def parse_subentries(self):
-        subentries = {}
-        while self.match_secondary():
-            subentry = self.parse_subentry()
-            subentries[subentry.id] = subentry
-        return subentries
-
-    def parse_subentry(self):
-        entry = Struct()
-        line = self.consume()
-        mo = self.entry_re.match(line)
-        if not mo:
-            raise ParseError('failed to parse', line)
-        fields = mo.groupdict()
-        entry.samples = int(fields.get('samples', 0))
-        entry.percentage = float(fields.get('percentage', 0.0))
-        if 'source' in fields and fields['source'] != '(no location information)':
-            source = fields['source']
-            filename, lineno = source.split(':')
-            entry.filename = filename
-            entry.lineno = int(lineno)
-        else:
-            source = ''
-            entry.filename = None
-            entry.lineno = None
-        entry.image = fields.get('image', '')
-        entry.application = fields.get('application', '')
-        if 'symbol' in fields and fields['symbol'] != '(no symbols)':
-            entry.symbol = fields['symbol']
-        else:
-            entry.symbol = ''
-        if entry.symbol.startswith('"') and entry.symbol.endswith('"'):
-            entry.symbol = entry.symbol[1:-1]
-        entry.id = ':'.join((entry.application, entry.image, source, entry.symbol))
-        entry.self = fields.get('self', None) != None
-        if entry.self:
-            entry.id += ':self'
-        if entry.symbol:
-            entry.name = entry.symbol
-        else:
-            entry.name = entry.image
-        return entry
-
-    def skip_separator(self):
-        while not self.match_separator():
-            self.consume()
-        self.consume()
-
-    def match_header(self):
-        line = self.lookahead()
-        return line.startswith('samples')
-
-    def match_separator(self):
-        line = self.lookahead()
-        return line == '-'*len(line)
-
-    def match_primary(self):
-        line = self.lookahead()
-        return not line[:1].isspace()
-    
-    def match_secondary(self):
-        line = self.lookahead()
-        return line[:1].isspace()
-
-
-class SharkParser(LineParser):
-    """Parser for MacOSX Shark output.
-
-    Author: tom@dbservice.com
-    """
-
-    def __init__(self, infile):
-        LineParser.__init__(self, infile)
-        self.stack = []
-        self.entries = {}
-
-    def add_entry(self, function):
-        try:
-            entry = self.entries[function.id]
-        except KeyError:
-            self.entries[function.id] = (function, { })
-        else:
-            function_total, callees_total = entry
-            function_total.samples += function.samples
-    
-    def add_callee(self, function, callee):
-        func, callees = self.entries[function.id]
-        try:
-            entry = callees[callee.id]
-        except KeyError:
-            callees[callee.id] = callee
-        else:
-            entry.samples += callee.samples
-        
-    def parse(self):
-        self.readline()
-        self.readline()
-        self.readline()
-        self.readline()
-
-        match = re.compile(r'(?P<prefix>[|+ ]*)(?P<samples>\d+), (?P<symbol>[^,]+), (?P<image>.*)')
-
-        while self.lookahead():
-            line = self.consume()
-            mo = match.match(line)
-            if not mo:
-                raise ParseError('failed to parse', line)
-
-            fields = mo.groupdict()
-            prefix = len(fields.get('prefix', 0)) / 2 - 1
-
-            symbol = str(fields.get('symbol', 0))
-            image = str(fields.get('image', 0))
-
-            entry = Struct()
-            entry.id = ':'.join([symbol, image])
-            entry.samples = int(fields.get('samples', 0))
-
-            entry.name = symbol
-            entry.image = image
-
-            # adjust the callstack
-            if prefix < len(self.stack):
-                del self.stack[prefix:]
-
-            if prefix == len(self.stack):
-                self.stack.append(entry)
-
-            # if the callstack has had an entry, it's this functions caller
-            if prefix > 0:
-                self.add_callee(self.stack[prefix - 1], entry)
-                
-            self.add_entry(entry)
-                
-        profile = Profile()
-        profile[SAMPLES] = 0
-        for _function, _callees in self.entries.itervalues():
-            function = Function(_function.id, _function.name)
-            function[SAMPLES] = _function.samples
-            profile.add_function(function)
-            profile[SAMPLES] += _function.samples
-
-            if _function.image:
-                function[MODULE] = os.path.basename(_function.image)
-
-            for _callee in _callees.itervalues():
-                call = Call(_callee.id)
-                call[SAMPLES] = _callee.samples
-                function.add_call(call)
-                
-        # compute derived data
-        profile.validate()
-        profile.find_cycles()
-        profile.ratio(TIME_RATIO, SAMPLES)
-        profile.call_ratios(SAMPLES)
-        profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
-        return profile
-
-
-class PstatsParser:
-    """Parser python profiling statistics saved with te pstats module."""
-
-    def __init__(self, *filename):
-        import pstats
-        self.stats = pstats.Stats(*filename)
-        self.profile = Profile()
-        self.function_ids = {}
-
-    def get_function_name(self, (filename, line, name)):
-        module = os.path.splitext(filename)[0]
-        module = os.path.basename(module)
-        return "%s:%d:%s" % (module, line, name)
-
-    def get_function(self, key):
-        try:
-            id = self.function_ids[key]
-        except KeyError:
-            id = len(self.function_ids)
-            name = self.get_function_name(key)
-            function = Function(id, name)
-            self.profile.functions[id] = function
-            self.function_ids[key] = id
-        else:
-            function = self.profile.functions[id]
-        return function
-
-    def parse(self):
-        self.profile[TIME] = 0.0
-        self.profile[TOTAL_TIME] = self.stats.total_tt
-        for fn, (cc, nc, tt, ct, callers) in self.stats.stats.iteritems():
-            callee = self.get_function(fn)
-            callee[CALLS] = nc
-            callee[TOTAL_TIME] = ct
-            callee[TIME] = tt
-            self.profile[TIME] += tt
-            self.profile[TOTAL_TIME] = max(self.profile[TOTAL_TIME], ct)
-            for fn, value in callers.iteritems():
-                caller = self.get_function(fn)
-                call = Call(callee.id)
-                if isinstance(value, tuple):
-                    for i in xrange(0, len(value), 4):
-                        nc, cc, tt, ct = value[i:i+4]
-                        if CALLS in call:
-                            call[CALLS] += cc
-                        else:
-                            call[CALLS] = cc
-
-                        if TOTAL_TIME in call:
-                            call[TOTAL_TIME] += ct
-                        else:
-                            call[TOTAL_TIME] = ct
-
-                else:
-                    call[CALLS] = value
-                    call[TOTAL_TIME] = ratio(value, nc)*ct
-
-                caller.add_call(call)
-        #self.stats.print_stats()
-        #self.stats.print_callees()
-
-        # Compute derived events
-        self.profile.validate()
-        self.profile.ratio(TIME_RATIO, TIME)
-        self.profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
-
-        return self.profile
-
-
-class Theme:
-
-    def __init__(self, 
-            bgcolor = (0.0, 0.0, 1.0),
-            mincolor = (0.0, 0.0, 0.0),
-            maxcolor = (0.0, 0.0, 1.0),
-            fontname = "Arial",
-            minfontsize = 10.0,
-            maxfontsize = 10.0,
-            minpenwidth = 0.5,
-            maxpenwidth = 4.0,
-            gamma = 2.2):
-        self.bgcolor = bgcolor
-        self.mincolor = mincolor
-        self.maxcolor = maxcolor
-        self.fontname = fontname
-        self.minfontsize = minfontsize
-        self.maxfontsize = maxfontsize
-        self.minpenwidth = minpenwidth
-        self.maxpenwidth = maxpenwidth
-        self.gamma = gamma
-
-    def graph_bgcolor(self):
-        return self.hsl_to_rgb(*self.bgcolor)
-
-    def graph_fontname(self):
-        return self.fontname
-
-    def graph_fontsize(self):
-        return self.minfontsize
-
-    def node_bgcolor(self, weight):
-        return self.color(weight)
-
-    def node_fgcolor(self, weight):
-        return self.graph_bgcolor()
-
-    def node_fontsize(self, weight):
-        return self.fontsize(weight)
-
-    def edge_color(self, weight):
-        return self.color(weight)
-
-    def edge_fontsize(self, weight):
-        return self.fontsize(weight)
-
-    def edge_penwidth(self, weight):
-        return max(weight*self.maxpenwidth, self.minpenwidth)
-
-    def edge_arrowsize(self, weight):
-        return 0.5 * math.sqrt(self.edge_penwidth(weight))
-
-    def fontsize(self, weight):
-        return max(weight**2 * self.maxfontsize, self.minfontsize)
-
-    def color(self, weight):
-        weight = min(max(weight, 0.0), 1.0)
-    
-        hmin, smin, lmin = self.mincolor
-        hmax, smax, lmax = self.maxcolor
-
-        h = hmin + weight*(hmax - hmin)
-        s = smin + weight*(smax - smin)
-        l = lmin + weight*(lmax - lmin)
-
-        return self.hsl_to_rgb(h, s, l)
-
-    def hsl_to_rgb(self, h, s, l):
-        """Convert a color from HSL color-model to RGB.
-
-        See also:
-        - http://www.w3.org/TR/css3-color/#hsl-color
-        """
-
-        h = h % 1.0
-        s = min(max(s, 0.0), 1.0)
-        l = min(max(l, 0.0), 1.0)
-
-        if l <= 0.5:
-            m2 = l*(s + 1.0)
-        else:
-            m2 = l + s - l*s
-        m1 = l*2.0 - m2
-        r = self._hue_to_rgb(m1, m2, h + 1.0/3.0)
-        g = self._hue_to_rgb(m1, m2, h)
-        b = self._hue_to_rgb(m1, m2, h - 1.0/3.0)
-
-        # Apply gamma correction
-        r **= self.gamma
-        g **= self.gamma
-        b **= self.gamma
-
-        return (r, g, b)
-
-    def _hue_to_rgb(self, m1, m2, h):
-        if h < 0.0:
-            h += 1.0
-        elif h > 1.0:
-            h -= 1.0
-        if h*6 < 1.0:
-            return m1 + (m2 - m1)*h*6.0
-        elif h*2 < 1.0:
-            return m2
-        elif h*3 < 2.0:
-            return m1 + (m2 - m1)*(2.0/3.0 - h)*6.0
-        else:
-            return m1
-
-
-TEMPERATURE_COLORMAP = Theme(
-    mincolor = (2.0/3.0, 0.80, 0.25), # dark blue
-    maxcolor = (0.0, 1.0, 0.5), # satured red
-    gamma = 1.0
-)
-
-PINK_COLORMAP = Theme(
-    mincolor = (0.0, 1.0, 0.90), # pink
-    maxcolor = (0.0, 1.0, 0.5), # satured red
-)
-
-GRAY_COLORMAP = Theme(
-    mincolor = (0.0, 0.0, 0.85), # light gray
-    maxcolor = (0.0, 0.0, 0.0), # black
-)
-
-BW_COLORMAP = Theme(
-    minfontsize = 8.0,
-    maxfontsize = 24.0,
-    mincolor = (0.0, 0.0, 0.0), # black
-    maxcolor = (0.0, 0.0, 0.0), # black
-    minpenwidth = 0.1,
-    maxpenwidth = 8.0,
-)
-
-
-class DotWriter:
-    """Writer for the DOT language.
-
-    See also:
-    - "The DOT Language" specification
-      http://www.graphviz.org/doc/info/lang.html
-    """
-
-    def __init__(self, fp):
-        self.fp = fp
-
-    def graph(self, profile, theme):
-        self.begin_graph()
-
-        fontname = theme.graph_fontname()
-
-        self.attr('graph', fontname=fontname, ranksep=0.25, nodesep=0.125)
-        self.attr('node', fontname=fontname, shape="box", style="filled,rounded", fontcolor="white", width=0, height=0)
-        self.attr('edge', fontname=fontname)
-
-        for function in profile.functions.itervalues():
-            labels = []
-            for event in PROCESS, MODULE:
-                if event in function.events:
-                    label = event.format(function[event])
-                    labels.append(label)
-            labels.append(function.name)
-            for event in TOTAL_TIME_RATIO, TIME_RATIO, CALLS:
-                if event in function.events:
-                    label = event.format(function[event])
-                    labels.append(label)
-
-            try:
-                weight = function[PRUNE_RATIO]
-            except UndefinedEvent:
-                weight = 0.0
-
-            label = '\n'.join(labels)
-            self.node(function.id, 
-                label = label, 
-                color = self.color(theme.node_bgcolor(weight)), 
-                fontcolor = self.color(theme.node_fgcolor(weight)), 
-                fontsize = "%.2f" % theme.node_fontsize(weight),
-            )
-
-            for call in function.calls.itervalues():
-                callee = profile.functions[call.callee_id]
-
-                labels = []
-                for event in TOTAL_TIME_RATIO, CALLS:
-                    if event in call.events:
-                        label = event.format(call[event])
-                        labels.append(label)
-
-                try:
-                    weight = call[PRUNE_RATIO]
-                except UndefinedEvent:
-                    try:
-                        weight = callee[PRUNE_RATIO]
-                    except UndefinedEvent:
-                        weight = 0.0
-
-                label = '\n'.join(labels)
-
-                self.edge(function.id, call.callee_id, 
-                    label = label, 
-                    color = self.color(theme.edge_color(weight)), 
-                    fontcolor = self.color(theme.edge_color(weight)),
-                    fontsize = "%.2f" % theme.edge_fontsize(weight), 
-                    penwidth = "%.2f" % theme.edge_penwidth(weight), 
-                    labeldistance = "%.2f" % theme.edge_penwidth(weight), 
-                    arrowsize = "%.2f" % theme.edge_arrowsize(weight),
-                )
-
-        self.end_graph()
-
-    def begin_graph(self):
-        self.write('digraph {\n')
-
-    def end_graph(self):
-        self.write('}\n')
-
-    def attr(self, what, **attrs):
-        self.write("\t")
-        self.write(what)
-        self.attr_list(attrs)
-        self.write(";\n")
-
-    def node(self, node, **attrs):
-        self.write("\t")
-        self.id(node)
-        self.attr_list(attrs)
-        self.write(";\n")
-
-    def edge(self, src, dst, **attrs):
-        self.write("\t")
-        self.id(src)
-        self.write(" -> ")
-        self.id(dst)
-        self.attr_list(attrs)
-        self.write(";\n")
-
-    def attr_list(self, attrs):
-        if not attrs:
-            return
-        self.write(' [')
-        first = True
-        for name, value in attrs.iteritems():
-            if first:
-                first = False
-            else:
-                self.write(", ")
-            self.id(name)
-            self.write('=')
-            self.id(value)
-        self.write(']')
-
-    def id(self, id):
-        if isinstance(id, (int, float)):
-            s = str(id)
-        elif isinstance(id, str):
-            if id.isalnum():
-                s = id
-            else:
-                s = self.escape(id)
-        else:
-            raise TypeError
-        self.write(s)
-
-    def color(self, (r, g, b)):
-
-        def float2int(f):
-            if f <= 0.0:
-                return 0
-            if f >= 1.0:
-                return 255
-            return int(255.0*f + 0.5)
-
-        return "#" + "".join(["%02x" % float2int(c) for c in (r, g, b)])
-
-    def escape(self, s):
-        s = s.encode('utf-8')
-        s = s.replace('\\', r'\\')
-        s = s.replace('\n', r'\n')
-        s = s.replace('\t', r'\t')
-        s = s.replace('"', r'\"')
-        return '"' + s + '"'
-
-    def write(self, s):
-        self.fp.write(s)
-
-
-class Main:
-    """Main program."""
-
-    themes = {
-            "color": TEMPERATURE_COLORMAP,
-            "pink": PINK_COLORMAP,
-            "gray": GRAY_COLORMAP,
-            "bw": BW_COLORMAP,
-    }
-
-    def main(self):
-        """Main program."""
-
-        parser = optparse.OptionParser(
-            usage="\n\t%prog [options] [file] ...",
-            version="%%prog %s" % __version__)
-        parser.add_option(
-            '-o', '--output', metavar='FILE',
-            type="string", dest="output",
-            help="output filename [stdout]")
-        parser.add_option(
-            '-n', '--node-thres', metavar='PERCENTAGE',
-            type="float", dest="node_thres", default=0.5,
-            help="eliminate nodes below this threshold [default: %default]")
-        parser.add_option(
-            '-e', '--edge-thres', metavar='PERCENTAGE',
-            type="float", dest="edge_thres", default=0.1,
-            help="eliminate edges below this threshold [default: %default]")
-        parser.add_option(
-            '-f', '--format',
-            type="choice", choices=('prof', 'oprofile', 'pstats', 'shark'),
-            dest="format", default="prof",
-            help="profile format: prof, oprofile, or pstats [default: %default]")
-        parser.add_option(
-            '-c', '--colormap',
-            type="choice", choices=('color', 'pink', 'gray', 'bw'),
-            dest="theme", default="color",
-            help="color map: color, pink, gray, or bw [default: %default]")
-        parser.add_option(
-            '-s', '--strip',
-            action="store_true",
-            dest="strip", default=False,
-            help="strip function parameters, template parameters, and const modifiers from demangled C++ function names")
-        parser.add_option(
-            '-w', '--wrap',
-            action="store_true",
-            dest="wrap", default=False,
-            help="wrap function names")
-        (self.options, self.args) = parser.parse_args(sys.argv[1:])
-
-        if len(self.args) > 1 and self.options.format != 'pstats':
-            parser.error('incorrect number of arguments')
-
-        try:
-            self.theme = self.themes[self.options.theme]
-        except KeyError:
-            parser.error('invalid colormap \'%s\'' % self.options.theme)
-
-        if self.options.format == 'prof':
-            if not self.args:
-                fp = sys.stdin
-            else:
-                fp = open(self.args[0], 'rt')
-            parser = GprofParser(fp)
-        elif self.options.format == 'oprofile':
-            if not self.args:
-                fp = sys.stdin
-            else:
-                fp = open(self.args[0], 'rt')
-            parser = OprofileParser(fp)
-        elif self.options.format == 'pstats':
-            if not self.args:
-                parser.error('at least a file must be specified for pstats input')
-            parser = PstatsParser(*self.args)
-        elif self.options.format == 'shark':
-            if not self.args:
-                fp = sys.stdin
-            else:
-                fp = open(self.args[0], 'rt')
-            parser = SharkParser(fp)
-        else:
-            parser.error('invalid format \'%s\'' % self.options.format)
-
-        self.profile = parser.parse()
-        
-        if self.options.output is None:
-            self.output = sys.stdout
-        else:
-            self.output = open(self.options.output, 'wt')
-
-        self.write_graph()
-
-    _parenthesis_re = re.compile(r'\([^()]*\)')
-    _angles_re = re.compile(r'<[^<>]*>')
-    _const_re = re.compile(r'\s+const$')
-
-    def strip_function_name(self, name):
-        """Remove extraneous information from C++ demangled function names."""
-
-        # Strip function parameters from name by recursively removing paired parenthesis
-        while True:
-            name, n = self._parenthesis_re.subn('', name)
-            if not n:
-                break
-
-        # Strip const qualifier
-        name = self._const_re.sub('', name)
-
-        # Strip template parameters from name by recursively removing paired angles
-        while True:
-            name, n = self._angles_re.subn('', name)
-            if not n:
-                break
-
-        return name
-
-    def wrap_function_name(self, name):
-        """Split the function name on multiple lines."""
-
-        if len(name) > 32:
-            ratio = 2.0/3.0
-            height = max(int(len(name)/(1.0 - ratio) + 0.5), 1)
-            width = max(len(name)/height, 32)
-            # TODO: break lines in symbols
-            name = textwrap.fill(name, width, break_long_words=False)
-
-        # Take away spaces
-        name = name.replace(", ", ",")
-        name = name.replace("> >", ">>")
-        name = name.replace("> >", ">>") # catch consecutive
-
-        return name
-
-    def compress_function_name(self, name):
-        """Compress function name according to the user preferences."""
-
-        if self.options.strip:
-            name = self.strip_function_name(name)
-
-        if self.options.wrap:
-            name = self.wrap_function_name(name)
-
-        # TODO: merge functions with same resulting name
-
-        return name
-
-    def write_graph(self):
-        dot = DotWriter(self.output)
-        profile = self.profile
-        profile.prune(self.options.node_thres/100.0, self.options.edge_thres/100.0)
-
-        for function in profile.functions.itervalues():
-            function.name = self.compress_function_name(function.name)
-
-        dot.graph(profile, self.theme)
-
-
-if __name__ == '__main__':
-    Main().main()
diff --git a/lib/tvdb_api/tests/runtests.py b/lib/tvdb_api/tests/runtests.py
deleted file mode 100644
index 35932cd3916787b1ceb0df3f4510d05457958d5d..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/tests/runtests.py
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env python2
-#encoding:utf-8
-#author:dbr/Ben
-#project:tvdb_api
-#repository:http://github.com/dbr/tvdb_api
-#license:unlicense (http://unlicense.org/)
-
-import sys
-import unittest
-
-import test_tvdb_api
-
-def main():
-    suite = unittest.TestSuite([
-        unittest.TestLoader().loadTestsFromModule(test_tvdb_api)
-    ])
-    
-    runner = unittest.TextTestRunner(verbosity=2)
-    result = runner.run(suite)
-    if result.wasSuccessful():
-        return 0
-    else:
-        return 1
-
-if __name__ == '__main__':
-    sys.exit(
-        int(main())
-    )
diff --git a/lib/tvdb_api/tests/test_tvdb_api.py b/lib/tvdb_api/tests/test_tvdb_api.py
deleted file mode 100644
index ad81726756c34a33c2b67f969d8bb60ee6ce79de..0000000000000000000000000000000000000000
--- a/lib/tvdb_api/tests/test_tvdb_api.py
+++ /dev/null
@@ -1,577 +0,0 @@
-#!/usr/bin/env python2
-#encoding:utf-8
-#author:dbr/Ben
-#project:tvdb_api
-#repository:http://github.com/dbr/tvdb_api
-#license:unlicense (http://unlicense.org/)
-
-"""Unittests for tvdb_api
-"""
-
-import os,os.path
-import sys
-print sys.path
-import datetime
-import unittest
-
-# Force parent directory onto path
-#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-
-sys.path.append(os.path.abspath('../../tests'))
-
-
-import sickbeard
-
-
-from tvdb_api import Tvdb
-import tvdb_ui
-from tvdb_api import (tvdb_shownotfound, tvdb_seasonnotfound,
-tvdb_episodenotfound, tvdb_attributenotfound)
-from lib import xmltodict
-import lib
-
-class test_tvdb_basic(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-    
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, banners = False)
-     
-    def test_different_case(self):
-        """Checks the auto-correction of show names is working.
-        It should correct the weirdly capitalised 'sCruBs' to 'Scrubs'
-        """
-        self.assertEquals(self.t['scrubs'][1][4]['episodename'], 'My Old Lady')
-        self.assertEquals(self.t['sCruBs']['seriesname'], 'Scrubs')
-
-    def test_spaces(self):
-        """Checks shownames with spaces
-        """
-        self.assertEquals(self.t['My Name Is Earl']['seriesname'], 'My Name Is Earl')
-        self.assertEquals(self.t['My Name Is Earl'][1][4]['episodename'], 'Faked His Own Death')
-
-    def test_numeric(self):
-        """Checks numeric show names
-        """
-        self.assertEquals(self.t['24'][2][20]['episodename'], 'Day 2: 3:00 A.M.-4:00 A.M.')
-        self.assertEquals(self.t['24']['seriesname'], '24')
-
-    def test_show_iter(self):
-        """Iterating over a show returns each seasons
-        """
-        self.assertEquals(
-            len(
-                [season for season in self.t['Life on Mars']]
-            ),
-            2
-        )
-    
-    def test_season_iter(self):
-        """Iterating over a show returns episodes
-        """
-        self.assertEquals(
-            len(
-                [episode for episode in self.t['Life on Mars'][1]]
-            ),
-            8
-        )
-
-    def test_get_episode_overview(self):
-        """Checks episode overview is retrieved correctly.
-        """
-        self.assertEquals(
-            self.t['Battlestar Galactica (2003)'][1][6]['overview'].startswith(
-                'When a new copy of Doral, a Cylon who had been previously'),
-            True
-        )
-
-    def test_get_parent(self):
-        """Check accessing series from episode instance
-        """
-        show = self.t['Battlestar Galactica (2003)']
-        season = show[1]
-        episode = show[1][1]
-
-        self.assertEquals(
-            season.show,
-            show
-        )
-
-        self.assertEquals(
-            episode.season,
-            season
-        )
-
-        self.assertEquals(
-            episode.season.show,
-            show
-        )
-
-    def test_no_season(self):
-        show = self.t['Katekyo Hitman Reborn']
-        print tvdb_api
-        print show[1][1]
-
-
-class test_tvdb_errors(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-    
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, banners = False)
-
-    def test_seasonnotfound(self):
-        """Checks exception is thrown when season doesn't exist.
-        """
-        self.assertRaises(tvdb_seasonnotfound, lambda:self.t['CNNNN'][10][1])
-
-    def test_shownotfound(self):
-        """Checks exception is thrown when episode doesn't exist.
-        """
-        self.assertRaises(tvdb_shownotfound, lambda:self.t['the fake show thingy'])
-    
-    def test_episodenotfound(self):
-        """Checks exception is raised for non-existent episode
-        """
-        self.assertRaises(tvdb_episodenotfound, lambda:self.t['Scrubs'][1][30])
-
-    def test_attributenamenotfound(self):
-        """Checks exception is thrown for if an attribute isn't found.
-        """
-        self.assertRaises(tvdb_attributenotfound, lambda:self.t['CNNNN'][1][6]['afakeattributething'])
-        self.assertRaises(tvdb_attributenotfound, lambda:self.t['CNNNN']['afakeattributething'])
-
-class test_tvdb_search(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-    
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, banners = False)
-
-    def test_search_len(self):
-        """There should be only one result matching
-        """
-        self.assertEquals(len(self.t['My Name Is Earl'].search('Faked His Own Death')), 1)
-
-    def test_search_checkname(self):
-        """Checks you can get the episode name of a search result
-        """
-        self.assertEquals(self.t['Scrubs'].search('my first')[0]['episodename'], 'My First Day')
-        self.assertEquals(self.t['My Name Is Earl'].search('Faked His Own Death')[0]['episodename'], 'Faked His Own Death')
-    
-    def test_search_multiresults(self):
-        """Checks search can return multiple results
-        """
-        self.assertEquals(len(self.t['Scrubs'].search('my first')) >= 3, True)
-
-    def test_search_no_params_error(self):
-        """Checks not supplying search info raises TypeError"""
-        self.assertRaises(
-            TypeError,
-            lambda: self.t['Scrubs'].search()
-        )
-
-    def test_search_season(self):
-        """Checks the searching of a single season"""
-        self.assertEquals(
-            len(self.t['Scrubs'][1].search("First")),
-            3
-        )
-    
-    def test_search_show(self):
-        """Checks the searching of an entire show"""
-        self.assertEquals(
-            len(self.t['CNNNN'].search('CNNNN', key='episodename')),
-            3
-        )
-
-    def test_aired_on(self):
-        """Tests airedOn show method"""
-        sr = self.t['Scrubs'].airedOn(datetime.date(2001, 10, 2))
-        self.assertEquals(len(sr), 1)
-        self.assertEquals(sr[0]['episodename'], u'My First Day')
-
-class test_tvdb_data(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-    
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, banners = False)
-
-    def test_episode_data(self):
-        """Check the firstaired value is retrieved
-        """
-        self.assertEquals(
-            self.t['lost']['firstaired'],
-            '2004-09-22'
-        )
-
-class test_tvdb_misc(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-    
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, banners = False)
-
-    def test_repr_show(self):
-        """Check repr() of Season
-        """
-        self.assertEquals(
-            repr(self.t['CNNNN']),
-            "<Show Chaser Non-Stop News Network (CNNNN) (containing 3 seasons)>"
-        )
-    def test_repr_season(self):
-        """Check repr() of Season
-        """
-        self.assertEquals(
-            repr(self.t['CNNNN'][1]),
-            "<Season instance (containing 9 episodes)>"
-        )
-    def test_repr_episode(self):
-        """Check repr() of Episode
-        """
-        self.assertEquals(
-            repr(self.t['CNNNN'][1][1]),
-            "<Episode 01x01 - Terror Alert>"
-        )
-    def test_have_all_languages(self):
-        """Check valid_languages is up-to-date (compared to languages.xml)
-        """
-        et = self.t._getetsrc(
-            "http://thetvdb.com/api/%s/languages.xml" % (
-                self.t.config['apikey']
-            )
-        )
-        languages = [x.find("abbreviation").text for x in et.findall("Language")]
-        
-        self.assertEquals(
-            sorted(languages),
-            sorted(self.t.config['valid_languages'])
-        )
-        
-class test_tvdb_languages(unittest.TestCase):
-    def test_episode_name_french(self):
-        """Check episode data is in French (language="fr")
-        """
-        t = tvdb_api.Tvdb(cache = True, language = "fr")
-        self.assertEquals(
-            t['scrubs'][1][1]['episodename'],
-            "Mon premier jour"
-        )
-        self.assertTrue(
-            t['scrubs']['overview'].startswith(
-                u"J.D. est un jeune m\xe9decin qui d\xe9bute"
-            )
-        )
-
-    def test_episode_name_spanish(self):
-        """Check episode data is in Spanish (language="es")
-        """
-        t = tvdb_api.Tvdb(cache = True, language = "es")
-        self.assertEquals(
-            t['scrubs'][1][1]['episodename'],
-            "Mi Primer Dia"
-        )
-        self.assertTrue(
-            t['scrubs']['overview'].startswith(
-                u'Scrubs es una divertida comedia'
-            )
-        )
-
-    def test_multilanguage_selection(self):
-        """Check selected language is used
-        """
-        class SelectEnglishUI(tvdb_ui.BaseUI):
-            def selectSeries(self, allSeries):
-                return [x for x in allSeries if x['language'] == "en"][0]
-
-        class SelectItalianUI(tvdb_ui.BaseUI):
-            def selectSeries(self, allSeries):
-                return [x for x in allSeries if x['language'] == "it"][0]
-
-        t_en = tvdb_api.Tvdb(
-            cache=True,
-            custom_ui = SelectEnglishUI,
-            language = "en")
-        t_it = tvdb_api.Tvdb(
-            cache=True,
-            custom_ui = SelectItalianUI,
-            language = "it")
-
-        self.assertEquals(
-            t_en['dexter'][1][2]['episodename'], "Crocodile"
-        )
-        self.assertEquals(
-            t_it['dexter'][1][2]['episodename'], "Lacrime di coccodrillo"
-        )
-
-
-class test_tvdb_unicode(unittest.TestCase):
-    def test_search_in_chinese(self):
-        """Check searching for show with language=zh returns Chinese seriesname
-        """
-        t = tvdb_api.Tvdb(cache = True, language = "zh")
-        show = t[u'T\xecnh Ng\u01b0\u1eddi Hi\u1ec7n \u0110\u1ea1i']
-        self.assertEquals(
-            type(show),
-            tvdb_api.Show
-        )
-        
-        self.assertEquals(
-            show['seriesname'],
-            u'T\xecnh Ng\u01b0\u1eddi Hi\u1ec7n \u0110\u1ea1i'
-        )
-
-    def test_search_in_all_languages(self):
-        """Check search_all_languages returns Chinese show, with language=en
-        """
-        t = tvdb_api.Tvdb(cache = True, search_all_languages = True, language="en")
-        show = t[u'T\xecnh Ng\u01b0\u1eddi Hi\u1ec7n \u0110\u1ea1i']
-        self.assertEquals(
-            type(show),
-            tvdb_api.Show
-        )
-        
-        self.assertEquals(
-            show['seriesname'],
-            u'Virtues Of Harmony II'
-        )
-
-class test_tvdb_banners(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-    
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, banners = True)
-
-    def test_have_banners(self):
-        """Check banners at least one banner is found
-        """
-        self.assertEquals(
-            len(self.t['scrubs']['_banners']) > 0,
-            True
-        )
-
-    def test_banner_url(self):
-        """Checks banner URLs start with http://
-        """
-        for banner_type, banner_data in self.t['scrubs']['_banners'].items():
-            for res, res_data in banner_data.items():
-                for bid, banner_info in res_data.items():
-                    self.assertEquals(
-                        banner_info['_bannerpath'].startswith("http://"),
-                        True
-                    )
-
-    def test_episode_image(self):
-        """Checks episode 'filename' image is fully qualified URL
-        """
-        self.assertEquals(
-            self.t['scrubs'][1][1]['filename'].startswith("http://"),
-            True
-        )
-    
-    def test_show_artwork(self):
-        """Checks various image URLs within season data are fully qualified
-        """
-        for key in ['banner', 'fanart', 'poster']:
-            self.assertEquals(
-                self.t['scrubs'][key].startswith("http://"),
-                True
-            )
-
-class test_tvdb_actors(unittest.TestCase):
-    t = None
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, actors = True)
-
-    def test_actors_is_correct_datatype(self):
-        """Check show/_actors key exists and is correct type"""
-        self.assertTrue(
-            isinstance(
-                self.t['scrubs']['_actors'],
-                tvdb_api.Actors
-            )
-        )
-    
-    def test_actors_has_actor(self):
-        """Check show has at least one Actor
-        """
-        self.assertTrue(
-            isinstance(
-                self.t['scrubs']['_actors'][0],
-                tvdb_api.Actor
-            )
-        )
-    
-    def test_actor_has_name(self):
-        """Check first actor has a name"""
-        self.assertEquals(
-            self.t['scrubs']['_actors'][0]['name'],
-            "Zach Braff"
-        )
-
-    def test_actor_image_corrected(self):
-        """Check image URL is fully qualified
-        """
-        for actor in self.t['scrubs']['_actors']:
-            if actor['image'] is not None:
-                # Actor's image can be None, it displays as the placeholder
-                # image on thetvdb.com
-                self.assertTrue(
-                    actor['image'].startswith("http://")
-                )
-
-class test_tvdb_doctest(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-    
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, banners = False)
-    
-    def test_doctest(self):
-        """Check docstring examples works"""
-        import doctest
-        doctest.testmod(tvdb_api)
-
-
-class test_tvdb_custom_caching(unittest.TestCase):
-    def test_true_false_string(self):
-        """Tests setting cache to True/False/string
-
-        Basic tests, only checking for errors
-        """
-
-        tvdb_api.Tvdb(cache = True)
-        tvdb_api.Tvdb(cache = False)
-        tvdb_api.Tvdb(cache = "/tmp")
-
-    def test_invalid_cache_option(self):
-        """Tests setting cache to invalid value
-        """
-
-        try:
-            tvdb_api.Tvdb(cache = 2.3)
-        except ValueError:
-            pass
-        else:
-            self.fail("Expected ValueError from setting cache to float")
-
-    def test_custom_urlopener(self):
-        class UsedCustomOpener(Exception):
-            pass
-
-        import urllib2
-        class TestOpener(urllib2.BaseHandler):
-            def default_open(self, request):
-                print request.get_method()
-                raise UsedCustomOpener("Something")
-
-        custom_opener = urllib2.build_opener(TestOpener())
-        t = tvdb_api.Tvdb(cache = custom_opener)
-        try:
-            t['scrubs']
-        except UsedCustomOpener:
-            pass
-        else:
-            self.fail("Did not use custom opener")
-
-class test_tvdb_by_id(unittest.TestCase):
-    t = None
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, actors = True)
-
-    def test_actors_is_correct_datatype(self):
-        """Check show/_actors key exists and is correct type"""
-        self.assertEquals(
-            self.t[76156]['seriesname'],
-            'Scrubs'
-            )
-
-
-class test_tvdb_zip(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, useZip = True)
-
-    def test_get_series_from_zip(self):
-        """
-        """
-        self.assertEquals(self.t['scrubs'][1][4]['episodename'], 'My Old Lady')
-        self.assertEquals(self.t['sCruBs']['seriesname'], 'Scrubs')
-
-    def test_spaces_from_zip(self):
-        """Checks shownames with spaces
-        """
-        self.assertEquals(self.t['My Name Is Earl']['seriesname'], 'My Name Is Earl')
-        self.assertEquals(self.t['My Name Is Earl'][1][4]['episodename'], 'Faked His Own Death')
-
-
-class test_tvdb_show_ordering(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t_dvd = None
-    t_air = None
-
-    def setUp(self):
-        if self.t_dvd is None:
-            self.t_dvd = tvdb_api.Tvdb(cache = True, useZip = True, dvdorder=True)
-
-        if self.t_air is None:
-            self.t_air = tvdb_api.Tvdb(cache = True, useZip = True)
-
-    def test_ordering(self):
-        """Test Tvdb.search method
-        """
-        self.assertEquals(u'The Train Job', self.t_air['Firefly'][1][1]['episodename'])
-        self.assertEquals(u'Serenity', self.t_dvd['Firefly'][1][1]['episodename'])
-
-        self.assertEquals(u'The Cat & the Claw (Part 1)', self.t_air['Batman The Animated Series'][1][1]['episodename'])
-        self.assertEquals(u'On Leather Wings', self.t_dvd['Batman The Animated Series'][1][1]['episodename'])
-
-class test_tvdb_show_search(unittest.TestCase):
-    # Used to store the cached instance of Tvdb()
-    t = None
-
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, useZip = True)
-
-    def test_search(self):
-        """Test Tvdb.search method
-        """
-        results = self.t.search("my name is earl")
-        all_ids = [x['seriesid'] for x in results]
-        self.assertTrue('75397' in all_ids)
-
-
-class test_tvdb_alt_names(unittest.TestCase):
-    t = None
-    def setUp(self):
-        if self.t is None:
-            self.__class__.t = tvdb_api.Tvdb(cache = True, actors = True)
-
-    def test_1(self):
-        """Tests basic access of series name alias
-        """
-        results = self.t.search("Don't Trust the B---- in Apartment 23")
-        series = results[0]
-        self.assertTrue(
-            'Apartment 23' in series['aliasnames']
-        )
-
-
-if __name__ == '__main__':
-    runner = unittest.TextTestRunner(verbosity = 2)
-    unittest.main(testRunner = runner)
diff --git a/lib/tvdb_api/tvdb_api.py b/lib/tvdb_api/tvdb_api.py
index 4e3da252cac745e58c9e33edd981340b99f30f09..c7843c734a2857f1c59473412ce9cddc5085ed7e 100644
--- a/lib/tvdb_api/tvdb_api.py
+++ b/lib/tvdb_api/tvdb_api.py
@@ -571,9 +571,11 @@ class Tvdb:
                         "https": self.config['proxy'],
                     }
 
-                resp = session.get(url, cache_auto=True, params=params)
+                resp = session.get(url.strip(), cache_auto=True, params=params)
             else:
-                resp = requests.get(url, params=params)
+                resp = requests.get(url.strip(), params=params)
+
+            resp.raise_for_status()
         except requests.exceptions.HTTPError, e:
             raise tvdb_error("HTTP error " + str(e.errno) + " while loading URL " + str(url))
         except requests.exceptions.ConnectionError, e:
@@ -604,24 +606,22 @@ class Tvdb:
                 except:
                     pass
 
-            return (key, value)
+            return key, value
 
-        if resp.ok:
-            if 'application/zip' in resp.headers.get("Content-Type", ''):
-                try:
-                    # TODO: The zip contains actors.xml and banners.xml, which are currently ignored [GH-20]
-                    log().debug("We recived a zip file unpacking now ...")
-                    zipdata = StringIO.StringIO()
-                    zipdata.write(resp.content)
-                    myzipfile = zipfile.ZipFile(zipdata)
-                    return xmltodict.parse(myzipfile.read('%s.xml' % language), postprocessor=process)
-                except zipfile.BadZipfile:
-                    raise tvdb_error("Bad zip file received from thetvdb.com, could not read it")
-            else:
-                try:
-                    return xmltodict.parse(resp.content.strip(), postprocessor=process)
-                except:
-                    return dict([(u'data', None)])
+        if 'application/zip' in resp.headers.get("Content-Type", ''):
+            try:
+                log().debug("We recived a zip file unpacking now ...")
+                zipdata = StringIO.StringIO()
+                zipdata.write(resp.content)
+                myzipfile = zipfile.ZipFile(zipdata)
+                return xmltodict.parse(myzipfile.read('%s.xml' % language), postprocessor=process)
+            except zipfile.BadZipfile:
+                raise tvdb_error("Bad zip file received from thetvdb.com, could not read it")
+        else:
+            try:
+                return xmltodict.parse(resp.content.decode('utf-8'), postprocessor=process)
+            except:
+                return dict([(u'data', None)])
 
     def _getetsrc(self, url, params=None, language=None):
         """Loads a URL using caching, returns an ElementTree of the source
@@ -797,18 +797,20 @@ class Tvdb:
             return
 
         cur_actors = Actors()
-        for curActorItem in actorsEt["actor"]:
+        for cur_actor in actorsEt['actor']:
             curActor = Actor()
-            for k, v in curActorItem.items():
+            for k, v in cur_actor.items():
+                if k is None or v is None:
+                    continue
+
                 k = k.lower()
-                if v is not None:
-                    if k == "image":
-                        v = self.config['url_artworkPrefix'] % (v)
-                    else:
-                        v = self._cleanData(v)
+                if k == "image":
+                    v = self.config['url_artworkPrefix'] % (v)
+                else:
+                    v = self._cleanData(v)
+
                 curActor[k] = v
             cur_actors.append(curActor)
-
         self._setShowData(sid, '_actors', cur_actors)
 
     def _getShowData(self, sid, language, getEpInfo=False):
@@ -839,7 +841,7 @@ class Tvdb:
 
         if not seriesInfoEt:
             log().debug('Series result returned zero')
-            raise tvdb_shownotfound("Show search returned zero results (cannot find show on TVDB)")
+            raise tvdb_error("Series result returned zero")
 
         # get series data
         for k, v in seriesInfoEt['series'].items():
diff --git a/lib/tvrage_api/AUTHORS b/lib/tvrage_api/AUTHORS
deleted file mode 100644
index 81e6190b413fb63f8034428410de5a73f6fe5080..0000000000000000000000000000000000000000
--- a/lib/tvrage_api/AUTHORS
+++ /dev/null
@@ -1,9 +0,0 @@
-Original Author:
-------------
-* Christian Kreutzer
-
-Contributors
-------------
-* topdeck (http://bitbucket.org/topdeck)
-* samueltardieu (http://bitbucket.org/samueltardieu)
-* chevox (https://bitbucket.org/chexov)
diff --git a/lib/tvrage_api/LICENSE b/lib/tvrage_api/LICENSE
deleted file mode 100644
index 87bfbee4536219bc2e6b75fecbe6de1a0ed4f72c..0000000000000000000000000000000000000000
--- a/lib/tvrage_api/LICENSE
+++ /dev/null
@@ -1,26 +0,0 @@
-Copyright (c) 2009, Christian Kreutzer
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-  * Redistributions of source code must retain the above copyright notice,
-    this list of conditions, and the following disclaimer.
-  * Redistributions in binary form must reproduce the above copyright notice,
-    this list of conditions, and the following disclaimer in the
-    documentation and/or other materials provided with the distribution.
-  * Neither the name of the author of this software nor the name of
-    contributors to this software may be used to endorse or promote products
-    derived from this software without specific prior written consent.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/lib/tvrage_api/setup.py b/lib/tvrage_api/setup.py
deleted file mode 100644
index ec5fcaa7e41be57309623e2b7c3fc8ca5a356e52..0000000000000000000000000000000000000000
--- a/lib/tvrage_api/setup.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env python
-
-import os
-
-from distutils.core import setup
-from tvrage import __version__, __author__, __license__
-
-setup(name='python-tvrage',
-      description='python client for the tvrage.com XML API',
-      long_description = file(
-          os.path.join(os.path.dirname(__file__),'README.rst')).read(),
-      license=__license__,
-      version=__version__,
-      author=__author__,
-      author_email='herr.kreutzer@gmail.com',
-      # url='http://bitbucket.org/ckreutzer/python-tvrage/',
-      url='https://github.com/ckreutzer/python-tvrage',
-      packages=['tvrage'],
-      install_requires = ["BeautifulSoup"],
-      classifiers = [
-          'Development Status :: 4 - Beta',
-          'Intended Audience :: Developers',
-          'License :: OSI Approved :: BSD License',
-          'Topic :: Software Development :: Libraries :: Python Modules',
-          'Programming Language :: Python',
-          'Operating System :: OS Independent'
-      ]
-     )
-
diff --git a/lib/tvrage_api/tvrage_api.py b/lib/tvrage_api/tvrage_api.py
index dd060e813a0d3de055908781f45fc1ff75b17761..8ed78223ecd9b725b7a96eed047b9c4def23f420 100644
--- a/lib/tvrage_api/tvrage_api.py
+++ b/lib/tvrage_api/tvrage_api.py
@@ -392,7 +392,7 @@ class TVRage:
 
         return os.path.join(tempfile.gettempdir(), "tvrage_api-%s" % (uid))
 
-    #@retry(tvrage_error)
+    @retry(tvrage_error)
     def _loadUrl(self, url, params=None):
         try:
             log().debug("Retrieving URL %s" % url)
@@ -411,6 +411,7 @@ class TVRage:
             else:
                 resp = requests.get(url.strip(), params=params)
 
+            resp.raise_for_status()
         except requests.exceptions.HTTPError, e:
             raise tvrage_error("HTTP error " + str(e.errno) + " while loading URL " + str(url))
         except requests.exceptions.ConnectionError, e:
@@ -438,6 +439,22 @@ class TVRage:
                 'seasonnum': 'episodenumber'
             }
 
+            status_map = {
+                'returning series': 'Continuing',
+                'canceled/ended': 'Ended',
+                'tbd/on the bubble': 'Continuing',
+                'in development': 'Continuing',
+                'new series': 'Continuing',
+                'never aired': 'Ended',
+                'final season': 'Continuing',
+                'on hiatus': 'Continuing',
+                'pilot ordered': 'Continuing',
+                'pilot rejected': 'Ended',
+                'canceled': 'Ended',
+                'ended': 'Ended',
+                '': 'Unknown',
+            }
+
             try:
                 key = name_map[key.lower()]
             except (ValueError, TypeError, KeyError):
@@ -446,8 +463,17 @@ class TVRage:
             # clean up value and do type changes
             if value:
                 if isinstance(value, dict):
+                    if key == 'status':
+                        try:
+                            value = status_map[str(value).lower()]
+                            if not value:
+                                raise
+                        except:
+                            value = 'Unknown'
+
                     if key == 'network':
                         value = value['#text']
+
                     if key == 'genre':
                         value = value['genre']
                         if not value:
@@ -456,6 +482,7 @@ class TVRage:
                             value = [value]
                         value = filter(None, value)
                         value = '|' + '|'.join(value) + '|'
+
                 try:
                     if key == 'firstaired' and value in "0000-00-00":
                         new_value = str(dt.date.fromordinal(1))
@@ -470,11 +497,10 @@ class TVRage:
 
             return (key, value)
 
-        if resp.ok:
-            try:
-                return xmltodict.parse(resp.content.strip(), postprocessor=remap_keys)
-            except:
-                return dict([(u'data', None)])
+        try:
+            return xmltodict.parse(resp.content.decode('utf-8'), postprocessor=remap_keys)
+        except:
+            return dict([(u'data', None)])
 
     def _getetsrc(self, url, params=None):
         """Loads a URL using caching, returns an ElementTree of the source
@@ -523,7 +549,7 @@ class TVRage:
         - Trailing whitespace
         """
 
-        if not isinstance(data, dict or list):
+        if isinstance(data, basestring):
             data = data.replace(u"&amp;", u"&")
             data = data.strip()
 
@@ -579,7 +605,7 @@ class TVRage:
 
         if not seriesInfoEt:
             log().debug('Series result returned zero')
-            raise tvrage_shownotfound("Show search returned zero results (cannot find show on TVRAGE)")
+            raise tvrage_error("Series result returned zero")
 
         # get series data
         for k, v in seriesInfoEt.items():
diff --git a/readme.md b/readme.md
index 848a0e21027e183be9c1fc6c9a4106ca7562cbe1..69bff7c375cb04cfbb797b8e857cfd953098471f 100644
--- a/readme.md
+++ b/readme.md
@@ -3,7 +3,7 @@ SickRage
 Video File Manager for TV Shows, It watches for new episodes of your favorite shows and when they are posted it does its magic.
 
 ## Branch Build Status
-[![Build Status](https://travis-ci.org/SiCKRAGETV/SickRage.svg?branch=master)](https://travis-ci.org/SiCKRAGETV/SickRage)
+[![Build Status](https://travis-ci.org/SiCKRAGETV/SickRage.svg?branch=develop)](https://travis-ci.org/SiCKRAGETV/SickRage)
 
 ## Features
  - XBMC library updates, poster/fanart downloads, and NFO/TBN generation
@@ -21,6 +21,10 @@ Video File Manager for TV Shows, It watches for new episodes of your favorite sh
  - Your tvshow.nfo files are now tagged with a indexer key so that SickBeard can easily tell if the shows info comes from TheTVDB or TVRage.
  - Sports shows are now able to be searched for..
 
+## Screenshots
+-[Desktop (Full-HD)](http://imgur.com/a/4fpBk)<br>
+-[Mobile](http://imgur.com/a/WPyG6)
+
 ## Dependencies
  To run SickRage from source you will need Python 2.6+ and Cheetah 2.1.0+.
 
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index 3571ff8a2ec833bc1a86e06f1e70aeee64d0fcda..25406c000a9a045962a8545f9d3bc505e3f6b84a 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -23,10 +23,10 @@ import datetime
 import socket
 import os
 import re
-import sys
 import os.path
 
 from threading import Lock
+import sys
 
 from github import Github
 
@@ -108,12 +108,14 @@ AUTO_UPDATE = False
 NOTIFY_ON_UPDATE = False
 CUR_COMMIT_HASH = None
 BRANCH = ''
+
+GIT_RESET = False
 GIT_REMOTE = ''
 GIT_REMOTE_URL = ''
 CUR_COMMIT_BRANCH = ''
-
 GIT_ORG = 'SiCKRAGETV'
 GIT_REPO = 'SickRage'
+GIT_PATH = None
 
 INIT_LOCK = Lock()
 started = False
@@ -145,6 +147,7 @@ ANON_REDIRECT = None
 
 USE_API = False
 API_KEY = None
+API_ROOT = None
 
 ENABLE_HTTPS = False
 HTTPS_CERT = None
@@ -161,8 +164,8 @@ SORT_ARTICLE = False
 DEBUG = False
 
 USE_LISTVIEW = False
-METADATA_XBMC = None
-METADATA_XBMC_12PLUS = None
+METADATA_KODI = None
+METADATA_KODI_12PLUS = None
 METADATA_MEDIABROWSER = None
 METADATA_PS3 = None
 METADATA_WDTV = None
@@ -203,6 +206,7 @@ TORRENT_DIR = None
 DOWNLOAD_PROPERS = False
 CHECK_PROPERS_INTERVAL = None
 ALLOW_HIGH_PRIORITY = False
+RANDOMIZE_PROVIDERS = False
 
 AUTOPOSTPROCESSER_FREQUENCY = None
 DAILYSEARCH_FREQUENCY = None
@@ -277,17 +281,17 @@ TORRENT_LABEL = ''
 TORRENT_LABEL_ANIME = ''
 TORRENT_VERIFY_CERT = False
 
-USE_XBMC = False
-XBMC_ALWAYS_ON = True
-XBMC_NOTIFY_ONSNATCH = False
-XBMC_NOTIFY_ONDOWNLOAD = False
-XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = False
-XBMC_UPDATE_LIBRARY = False
-XBMC_UPDATE_FULL = False
-XBMC_UPDATE_ONLYFIRST = False
-XBMC_HOST = ''
-XBMC_USERNAME = None
-XBMC_PASSWORD = None
+USE_KODI = False
+KODI_ALWAYS_ON = True
+KODI_NOTIFY_ONSNATCH = False
+KODI_NOTIFY_ONDOWNLOAD = False
+KODI_NOTIFY_ONSUBTITLEDOWNLOAD = False
+KODI_UPDATE_LIBRARY = False
+KODI_UPDATE_FULL = False
+KODI_UPDATE_ONLYFIRST = False
+KODI_HOST = ''
+KODI_USERNAME = None
+KODI_PASSWORD = None
 
 USE_PLEX = False
 PLEX_NOTIFY_ONSNATCH = False
@@ -451,14 +455,13 @@ SUBTITLES_SERVICES_LIST = []
 SUBTITLES_SERVICES_ENABLED = []
 SUBTITLES_HISTORY = False
 SUBTITLES_FINDER_FREQUENCY = 1
+SUBTITLES_MULTI = False
 
 USE_FAILED_DOWNLOADS = False
 DELETE_FAILED = False
 
 EXTRA_SCRIPTS = []
 
-GIT_PATH = None
-
 IGNORE_WORDS = "german,french,core2hd,dutch,swedish,reenc,MrLss"
 REQUIRE_WORDS = ""
 
@@ -478,13 +481,13 @@ def get_backlog_cycle_time():
 def initialize(consoleLogging=True):
     with INIT_LOCK:
 
-        global BRANCH, GIT_REMOTE, GIT_REMOTE_URL, CUR_COMMIT_HASH, CUR_COMMIT_BRANCH, ACTUAL_LOG_DIR, LOG_DIR, WEB_PORT, WEB_LOG, ENCRYPTION_VERSION, WEB_ROOT, WEB_USERNAME, WEB_PASSWORD, WEB_HOST, WEB_IPV6, USE_API, API_KEY, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
-            HANDLE_REVERSE_PROXY, USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, CHECK_PROPERS_INTERVAL, ALLOW_HIGH_PRIORITY, TORRENT_METHOD, \
+        global BRANCH, GIT_RESET, GIT_REMOTE, GIT_REMOTE_URL, CUR_COMMIT_HASH, CUR_COMMIT_BRANCH, ACTUAL_LOG_DIR, LOG_DIR, WEB_PORT, WEB_LOG, ENCRYPTION_VERSION, WEB_ROOT, WEB_USERNAME, WEB_PASSWORD, WEB_HOST, WEB_IPV6, USE_API, API_KEY, API_ROOT, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
+            HANDLE_REVERSE_PROXY, USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, RANDOMIZE_PROVIDERS, CHECK_PROPERS_INTERVAL, ALLOW_HIGH_PRIORITY, TORRENT_METHOD, \
             SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_CATEGORY_ANIME, SAB_HOST, \
             NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_CATEGORY_ANIME, NZBGET_PRIORITY, NZBGET_HOST, NZBGET_USE_HTTPS, backlogSearchScheduler, \
             TORRENT_USERNAME, TORRENT_PASSWORD, TORRENT_HOST, TORRENT_PATH, TORRENT_SEED_TIME, TORRENT_PAUSED, TORRENT_HIGH_BANDWIDTH, TORRENT_LABEL, TORRENT_LABEL_ANIME, TORRENT_VERIFY_CERT, \
-            USE_XBMC, XBMC_ALWAYS_ON, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, \
-            XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, BACKLOG_FREQUENCY, \
+            USE_KODI, KODI_ALWAYS_ON, KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, \
+            KODI_UPDATE_LIBRARY, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, BACKLOG_FREQUENCY, \
             USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_API, TRAKT_REMOVE_WATCHLIST, TRAKT_USE_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, TRAKT_USE_RECOMMENDED, TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, \
             USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, \
             PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \
@@ -510,12 +513,12 @@ def initialize(consoleLogging=True):
             USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_NMJv2, NMJv2_HOST, NMJv2_DATABASE, NMJv2_DBLOC, USE_SYNOINDEX, \
             USE_SYNOLOGYNOTIFIER, SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH, SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD, SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD, \
             USE_EMAIL, EMAIL_HOST, EMAIL_PORT, EMAIL_TLS, EMAIL_USER, EMAIL_PASSWORD, EMAIL_FROM, EMAIL_NOTIFY_ONSNATCH, EMAIL_NOTIFY_ONDOWNLOAD, EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD, EMAIL_LIST, \
-            USE_LISTVIEW, METADATA_XBMC, METADATA_XBMC_12PLUS, METADATA_MEDIABROWSER, METADATA_PS3, metadata_provider_dict, \
+            USE_LISTVIEW, METADATA_KODI, METADATA_KODI_12PLUS, METADATA_MEDIABROWSER, METADATA_PS3, metadata_provider_dict, \
             NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, POSTPONE_IF_SYNC_FILES, dailySearchScheduler, NFO_RENAME, \
             GUI_NAME, HOME_LAYOUT, HISTORY_LAYOUT, DISPLAY_SHOW_SPECIALS, COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, COMING_EPS_MISSED_RANGE, FUZZY_DATING, TRIM_ZERO, DATE_PRESET, TIME_PRESET, TIME_PRESET_W_SECONDS, THEME_NAME, \
             POSTER_SORTBY, POSTER_SORTDIR, \
             METADATA_WDTV, METADATA_TIVO, METADATA_MEDE8ER, IGNORE_WORDS, REQUIRE_WORDS, CALENDAR_UNPROTECTED, CREATE_MISSING_SHOW_DIRS, \
-            ADD_SHOWS_WO_DIR, USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, subtitlesFinderScheduler, \
+            ADD_SHOWS_WO_DIR, USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, SUBTITLES_MULTI, subtitlesFinderScheduler, \
             USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, TMDB_API_KEY, DEBUG, PROXY_SETTING, PROXY_INDEXERS, \
             AUTOPOSTPROCESSER_FREQUENCY, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY, \
             ANIME_DEFAULT, NAMING_ANIME, ANIMESUPPORT, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST, \
@@ -529,7 +532,7 @@ def initialize(consoleLogging=True):
         CheckSection(CFG, 'Newzbin')
         CheckSection(CFG, 'SABnzbd')
         CheckSection(CFG, 'NZBget')
-        CheckSection(CFG, 'XBMC')
+        CheckSection(CFG, 'KODI')
         CheckSection(CFG, 'PLEX')
         CheckSection(CFG, 'Growl')
         CheckSection(CFG, 'Prowl')
@@ -546,9 +549,28 @@ def initialize(consoleLogging=True):
         CheckSection(CFG, 'Pushbullet')
         CheckSection(CFG, 'Subtitles')
 
+        # debugging
+        DEBUG = bool(check_setting_int(CFG, 'General', 'debug', 0))
+
+        ACTUAL_LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', 'Logs')
+        LOG_DIR = os.path.normpath(os.path.join(DATA_DIR, ACTUAL_LOG_DIR))
+
+        fileLogging = True
+        if not helpers.makeDir(LOG_DIR):
+            sys.stderr.write("!!! No log folder, logging to screen only!\n")
+            fileLogging=False
+
+        # init logging
+        logger.initLogging(consoleLogging=consoleLogging, fileLogging=fileLogging, debugLogging=DEBUG)
+
         # github api
-        gh = Github().get_organization(GIT_ORG).get_repo(GIT_REPO)  # wanted branch
+        try:gh = Github().get_organization(GIT_ORG).get_repo(GIT_REPO)
+        except:gh = None
+
+        # git reset on update
+        GIT_RESET = bool(check_setting_int(CFG, 'General', 'git_reset', 0))
 
+        # current git branch
         BRANCH = check_setting_str(CFG, 'General', 'branch', '')
 
         # git_remote
@@ -586,13 +608,6 @@ def initialize(consoleLogging=True):
 
         THEME_NAME = check_setting_str(CFG, 'GUI', 'theme_name', 'dark')
 
-        ACTUAL_LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', 'Logs')
-        # put the log dir inside the data dir, unless an absolute path
-        LOG_DIR = os.path.normpath(os.path.join(DATA_DIR, ACTUAL_LOG_DIR))
-
-        if not helpers.makeDir(LOG_DIR):
-            logger.log(u"!!! No log folder, logging to screen only!", logger.ERROR)
-
         SOCKET_TIMEOUT = check_setting_int(CFG, 'General', 'socket_timeout', 30)
         socket.setdefaulttimeout(SOCKET_TIMEOUT)
 
@@ -609,8 +624,8 @@ def initialize(consoleLogging=True):
         WEB_ROOT = check_setting_str(CFG, 'General', 'web_root', '').rstrip("/")
         WEB_LOG = bool(check_setting_int(CFG, 'General', 'web_log', 0))
         ENCRYPTION_VERSION = check_setting_int(CFG, 'General', 'encryption_version', 0)
-        WEB_USERNAME = check_setting_str(CFG, 'General', 'web_username', '')
-        WEB_PASSWORD = check_setting_str(CFG, 'General', 'web_password', '')
+        WEB_USERNAME = check_setting_str(CFG, 'General', 'web_username', '', censor_log=True)
+        WEB_PASSWORD = check_setting_str(CFG, 'General', 'web_password', '', censor_log=True)
         LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1))
 
         PLAY_VIDEOS = bool(check_setting_int(CFG, 'General', 'play_videos', 0))
@@ -634,9 +649,7 @@ def initialize(consoleLogging=True):
         SORT_ARTICLE = bool(check_setting_int(CFG, 'General', 'sort_article', 0))
 
         USE_API = bool(check_setting_int(CFG, 'General', 'use_api', 0))
-        API_KEY = check_setting_str(CFG, 'General', 'api_key', '')
-
-        DEBUG = bool(check_setting_int(CFG, 'General', 'debug', 0))
+        API_KEY = check_setting_str(CFG, 'General', 'api_key', '', censor_log=True)
 
         ENABLE_HTTPS = bool(check_setting_int(CFG, 'General', 'enable_https', 0))
 
@@ -692,6 +705,8 @@ def initialize(consoleLogging=True):
         if CHECK_PROPERS_INTERVAL not in ('15m', '45m', '90m', '4h', 'daily'):
             CHECK_PROPERS_INTERVAL = 'daily'
 
+        RANDOMIZE_PROVIDERS = bool(check_setting_int(CFG, 'General', 'randomize_providers', 0))
+
         ALLOW_HIGH_PRIORITY = bool(check_setting_int(CFG, 'General', 'allow_high_priority', 1))
 
         DAILYSEARCH_STARTUP = bool(check_setting_int(CFG, 'General', 'dailysearch_startup', 1))
@@ -738,30 +753,30 @@ def initialize(consoleLogging=True):
         ADD_SHOWS_WO_DIR = bool(check_setting_int(CFG, 'General', 'add_shows_wo_dir', 0))
 
         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', '')
+        NZBS_UID = check_setting_str(CFG, 'NZBs', 'nzbs_uid', '', censor_log=True)
+        NZBS_HASH = check_setting_str(CFG, 'NZBs', 'nzbs_hash', '', censor_log=True)
 
         NEWZBIN = bool(check_setting_int(CFG, 'Newzbin', 'newzbin', 0))
-        NEWZBIN_USERNAME = check_setting_str(CFG, 'Newzbin', 'newzbin_username', '')
-        NEWZBIN_PASSWORD = check_setting_str(CFG, 'Newzbin', 'newzbin_password', '')
+        NEWZBIN_USERNAME = check_setting_str(CFG, 'Newzbin', 'newzbin_username', '', censor_log=True)
+        NEWZBIN_PASSWORD = check_setting_str(CFG, 'Newzbin', 'newzbin_password', '', censor_log=True)
 
-        SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '')
-        SAB_PASSWORD = check_setting_str(CFG, 'SABnzbd', 'sab_password', '')
-        SAB_APIKEY = check_setting_str(CFG, 'SABnzbd', 'sab_apikey', '')
+        SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '', censor_log=True)
+        SAB_PASSWORD = check_setting_str(CFG, 'SABnzbd', 'sab_password', '', censor_log=True)
+        SAB_APIKEY = check_setting_str(CFG, 'SABnzbd', 'sab_apikey', '', censor_log=True)
         SAB_CATEGORY = check_setting_str(CFG, 'SABnzbd', 'sab_category', 'tv')
         SAB_CATEGORY_ANIME = check_setting_str(CFG, 'SABnzbd', 'sab_category_anime', 'anime')
         SAB_HOST = check_setting_str(CFG, 'SABnzbd', 'sab_host', '')
 
-        NZBGET_USERNAME = check_setting_str(CFG, 'NZBget', 'nzbget_username', 'nzbget')
-        NZBGET_PASSWORD = check_setting_str(CFG, 'NZBget', 'nzbget_password', 'tegbzn6789')
+        NZBGET_USERNAME = check_setting_str(CFG, 'NZBget', 'nzbget_username', 'nzbget', censor_log=True)
+        NZBGET_PASSWORD = check_setting_str(CFG, 'NZBget', 'nzbget_password', 'tegbzn6789', censor_log=True)
         NZBGET_CATEGORY = check_setting_str(CFG, 'NZBget', 'nzbget_category', 'tv')
         NZBGET_CATEGORY_ANIME = check_setting_str(CFG, 'NZBget', 'nzbget_category_anime', 'anime')
         NZBGET_HOST = check_setting_str(CFG, 'NZBget', 'nzbget_host', '')
         NZBGET_USE_HTTPS = bool(check_setting_int(CFG, 'NZBget', 'nzbget_use_https', 0))
         NZBGET_PRIORITY = check_setting_int(CFG, 'NZBget', 'nzbget_priority', 100)
 
-        TORRENT_USERNAME = check_setting_str(CFG, 'TORRENT', 'torrent_username', '')
-        TORRENT_PASSWORD = check_setting_str(CFG, 'TORRENT', 'torrent_password', '')
+        TORRENT_USERNAME = check_setting_str(CFG, 'TORRENT', 'torrent_username', '', censor_log=True)
+        TORRENT_PASSWORD = check_setting_str(CFG, 'TORRENT', 'torrent_password', '', censor_log=True)
         TORRENT_HOST = check_setting_str(CFG, 'TORRENT', 'torrent_host', '')
         TORRENT_PATH = check_setting_str(CFG, 'TORRENT', 'torrent_path', '')
         TORRENT_SEED_TIME = check_setting_int(CFG, 'TORRENT', 'torrent_seed_time', 0)
@@ -771,17 +786,17 @@ def initialize(consoleLogging=True):
         TORRENT_LABEL_ANIME = check_setting_str(CFG, 'TORRENT', 'torrent_label_anime', '')
         TORRENT_VERIFY_CERT = bool(check_setting_int(CFG, 'TORRENT', 'torrent_verify_cert', 0))
 
-        USE_XBMC = bool(check_setting_int(CFG, 'XBMC', 'use_xbmc', 0))
-        XBMC_ALWAYS_ON = bool(check_setting_int(CFG, 'XBMC', 'xbmc_always_on', 1))
-        XBMC_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsnatch', 0))
-        XBMC_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_ondownload', 0))
-        XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsubtitledownload', 0))
-        XBMC_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_library', 0))
-        XBMC_UPDATE_FULL = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_full', 0))
-        XBMC_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_onlyfirst', 0))
-        XBMC_HOST = check_setting_str(CFG, 'XBMC', 'xbmc_host', '')
-        XBMC_USERNAME = check_setting_str(CFG, 'XBMC', 'xbmc_username', '')
-        XBMC_PASSWORD = check_setting_str(CFG, 'XBMC', 'xbmc_password', '')
+        USE_KODI = bool(check_setting_int(CFG, 'KODI', 'use_kodi', 0))
+        KODI_ALWAYS_ON = bool(check_setting_int(CFG, 'KODI', 'kodi_always_on', 1))
+        KODI_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'KODI', 'kodi_notify_onsnatch', 0))
+        KODI_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'KODI', 'kodi_notify_ondownload', 0))
+        KODI_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'KODI', 'kodi_notify_onsubtitledownload', 0))
+        KODI_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'KODI', 'kodi_update_library', 0))
+        KODI_UPDATE_FULL = bool(check_setting_int(CFG, 'KODI', 'kodi_update_full', 0))
+        KODI_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'KODI', 'kodi_update_onlyfirst', 0))
+        KODI_HOST = check_setting_str(CFG, 'KODI', 'kodi_host', '')
+        KODI_USERNAME = check_setting_str(CFG, 'KODI', 'kodi_username', '', censor_log=True)
+        KODI_PASSWORD = check_setting_str(CFG, 'KODI', 'kodi_password', '', censor_log=True)
 
         USE_PLEX = bool(check_setting_int(CFG, 'Plex', 'use_plex', 0))
         PLEX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Plex', 'plex_notify_onsnatch', 0))
@@ -790,21 +805,21 @@ def initialize(consoleLogging=True):
         PLEX_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Plex', 'plex_update_library', 0))
         PLEX_SERVER_HOST = check_setting_str(CFG, 'Plex', 'plex_server_host', '')
         PLEX_HOST = check_setting_str(CFG, 'Plex', 'plex_host', '')
-        PLEX_USERNAME = check_setting_str(CFG, 'Plex', 'plex_username', '')
-        PLEX_PASSWORD = check_setting_str(CFG, 'Plex', 'plex_password', '')
+        PLEX_USERNAME = check_setting_str(CFG, 'Plex', 'plex_username', '', censor_log=True)
+        PLEX_PASSWORD = check_setting_str(CFG, 'Plex', 'plex_password', '', censor_log=True)
 
         USE_GROWL = bool(check_setting_int(CFG, 'Growl', 'use_growl', 0))
         GROWL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Growl', 'growl_notify_onsnatch', 0))
         GROWL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Growl', 'growl_notify_ondownload', 0))
         GROWL_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Growl', 'growl_notify_onsubtitledownload', 0))
         GROWL_HOST = check_setting_str(CFG, 'Growl', 'growl_host', '')
-        GROWL_PASSWORD = check_setting_str(CFG, 'Growl', 'growl_password', '')
+        GROWL_PASSWORD = check_setting_str(CFG, 'Growl', 'growl_password', '', censor_log=True)
 
         USE_PROWL = bool(check_setting_int(CFG, 'Prowl', 'use_prowl', 0))
         PROWL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_onsnatch', 0))
         PROWL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_ondownload', 0))
         PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Prowl', 'prowl_notify_onsubtitledownload', 0))
-        PROWL_API = check_setting_str(CFG, 'Prowl', 'prowl_api', '')
+        PROWL_API = check_setting_str(CFG, 'Prowl', 'prowl_api', '', censor_log=True)
         PROWL_PRIORITY = check_setting_str(CFG, 'Prowl', 'prowl_priority', "0")
 
         USE_TWITTER = bool(check_setting_int(CFG, 'Twitter', 'use_twitter', 0))
@@ -812,30 +827,30 @@ def initialize(consoleLogging=True):
         TWITTER_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Twitter', 'twitter_notify_ondownload', 0))
         TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD = bool(
             check_setting_int(CFG, 'Twitter', 'twitter_notify_onsubtitledownload', 0))
-        TWITTER_USERNAME = check_setting_str(CFG, 'Twitter', 'twitter_username', '')
-        TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '')
+        TWITTER_USERNAME = check_setting_str(CFG, 'Twitter', 'twitter_username', '', censor_log=True)
+        TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '', censor_log=True)
         TWITTER_PREFIX = check_setting_str(CFG, 'Twitter', 'twitter_prefix', 'SickRage')
 
         USE_BOXCAR = bool(check_setting_int(CFG, 'Boxcar', 'use_boxcar', 0))
         BOXCAR_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_onsnatch', 0))
         BOXCAR_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_ondownload', 0))
         BOXCAR_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_onsubtitledownload', 0))
-        BOXCAR_USERNAME = check_setting_str(CFG, 'Boxcar', 'boxcar_username', '')
+        BOXCAR_USERNAME = check_setting_str(CFG, 'Boxcar', 'boxcar_username', '', censor_log=True)
 
         USE_BOXCAR2 = bool(check_setting_int(CFG, 'Boxcar2', 'use_boxcar2', 0))
         BOXCAR2_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar2', 'boxcar2_notify_onsnatch', 0))
         BOXCAR2_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Boxcar2', 'boxcar2_notify_ondownload', 0))
         BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = bool(
             check_setting_int(CFG, 'Boxcar2', 'boxcar2_notify_onsubtitledownload', 0))
-        BOXCAR2_ACCESSTOKEN = check_setting_str(CFG, 'Boxcar2', 'boxcar2_accesstoken', '')
+        BOXCAR2_ACCESSTOKEN = check_setting_str(CFG, 'Boxcar2', 'boxcar2_accesstoken', '', censor_log=True)
 
         USE_PUSHOVER = bool(check_setting_int(CFG, 'Pushover', 'use_pushover', 0))
         PUSHOVER_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Pushover', 'pushover_notify_onsnatch', 0))
         PUSHOVER_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Pushover', 'pushover_notify_ondownload', 0))
         PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = bool(
             check_setting_int(CFG, 'Pushover', 'pushover_notify_onsubtitledownload', 0))
-        PUSHOVER_USERKEY = check_setting_str(CFG, 'Pushover', 'pushover_userkey', '')
-        PUSHOVER_APIKEY = check_setting_str(CFG, 'Pushover', 'pushover_apikey', '')
+        PUSHOVER_USERKEY = check_setting_str(CFG, 'Pushover', 'pushover_userkey', '', censor_log=True)
+        PUSHOVER_APIKEY = check_setting_str(CFG, 'Pushover', 'pushover_apikey', '', censor_log=True)
         USE_LIBNOTIFY = bool(check_setting_int(CFG, 'Libnotify', 'use_libnotify', 0))
         LIBNOTIFY_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_onsnatch', 0))
         LIBNOTIFY_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_ondownload', 0))
@@ -863,9 +878,9 @@ def initialize(consoleLogging=True):
             check_setting_int(CFG, 'SynologyNotifier', 'synologynotifier_notify_onsubtitledownload', 0))
 
         USE_TRAKT = bool(check_setting_int(CFG, 'Trakt', 'use_trakt', 0))
-        TRAKT_USERNAME = check_setting_str(CFG, 'Trakt', 'trakt_username', '')
-        TRAKT_PASSWORD = check_setting_str(CFG, 'Trakt', 'trakt_password', '')
-        TRAKT_API = check_setting_str(CFG, 'Trakt', 'trakt_api', '')
+        TRAKT_USERNAME = check_setting_str(CFG, 'Trakt', 'trakt_username', '', censor_log=True)
+        TRAKT_PASSWORD = check_setting_str(CFG, 'Trakt', 'trakt_password', '', censor_log=True)
+        TRAKT_API = check_setting_str(CFG, 'Trakt', 'trakt_api', '', censor_log=True)
         TRAKT_REMOVE_WATCHLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_remove_watchlist', 0))
         TRAKT_REMOVE_SERIESLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_remove_serieslist', 0))
         TRAKT_USE_WATCHLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_use_watchlist', 0))
@@ -889,7 +904,7 @@ def initialize(consoleLogging=True):
         NMA_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'NMA', 'nma_notify_onsnatch', 0))
         NMA_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'NMA', 'nma_notify_ondownload', 0))
         NMA_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'NMA', 'nma_notify_onsubtitledownload', 0))
-        NMA_API = check_setting_str(CFG, 'NMA', 'nma_api', '')
+        NMA_API = check_setting_str(CFG, 'NMA', 'nma_api', '', censor_log=True)
         NMA_PRIORITY = check_setting_str(CFG, 'NMA', 'nma_priority', "0")
 
         USE_PUSHALOT = bool(check_setting_int(CFG, 'Pushalot', 'use_pushalot', 0))
@@ -897,14 +912,14 @@ def initialize(consoleLogging=True):
         PUSHALOT_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Pushalot', 'pushalot_notify_ondownload', 0))
         PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = bool(
             check_setting_int(CFG, 'Pushalot', 'pushalot_notify_onsubtitledownload', 0))
-        PUSHALOT_AUTHORIZATIONTOKEN = check_setting_str(CFG, 'Pushalot', 'pushalot_authorizationtoken', '')
+        PUSHALOT_AUTHORIZATIONTOKEN = check_setting_str(CFG, 'Pushalot', 'pushalot_authorizationtoken', '', censor_log=True)
 
         USE_PUSHBULLET = bool(check_setting_int(CFG, 'Pushbullet', 'use_pushbullet', 0))
         PUSHBULLET_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Pushbullet', 'pushbullet_notify_onsnatch', 0))
         PUSHBULLET_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Pushbullet', 'pushbullet_notify_ondownload', 0))
         PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = bool(
             check_setting_int(CFG, 'Pushbullet', 'pushbullet_notify_onsubtitledownload', 0))
-        PUSHBULLET_API = check_setting_str(CFG, 'Pushbullet', 'pushbullet_api', '')
+        PUSHBULLET_API = check_setting_str(CFG, 'Pushbullet', 'pushbullet_api', '', censor_log=True)
         PUSHBULLET_DEVICE = check_setting_str(CFG, 'Pushbullet', 'pushbullet_device', '')
 
         USE_EMAIL = bool(check_setting_int(CFG, 'Email', 'use_email', 0))
@@ -914,8 +929,8 @@ def initialize(consoleLogging=True):
         EMAIL_HOST = check_setting_str(CFG, 'Email', 'email_host', '')
         EMAIL_PORT = check_setting_int(CFG, 'Email', 'email_port', 25)
         EMAIL_TLS = bool(check_setting_int(CFG, 'Email', 'email_tls', 0))
-        EMAIL_USER = check_setting_str(CFG, 'Email', 'email_user', '')
-        EMAIL_PASSWORD = check_setting_str(CFG, 'Email', 'email_password', '')
+        EMAIL_USER = check_setting_str(CFG, 'Email', 'email_user', '', censor_log=True)
+        EMAIL_PASSWORD = check_setting_str(CFG, 'Email', 'email_password', '', censor_log=True)
         EMAIL_FROM = check_setting_str(CFG, 'Email', 'email_from', '')
         EMAIL_LIST = check_setting_str(CFG, 'Email', 'email_list', '')
 
@@ -931,6 +946,7 @@ def initialize(consoleLogging=True):
         SUBTITLES_DEFAULT = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_default', 0))
         SUBTITLES_HISTORY = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_history', 0))
         SUBTITLES_FINDER_FREQUENCY = check_setting_int(CFG, 'Subtitles', 'subtitles_finder_frequency', 1)
+        SUBTITLES_MULTI = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_multi', 1))
 
         USE_FAILED_DOWNLOADS = bool(check_setting_int(CFG, 'FailedDownloads', 'use_failed_downloads', 0))
         DELETE_FAILED = bool(check_setting_int(CFG, 'FailedDownloads', 'delete_failed', 0))
@@ -949,14 +965,14 @@ def initialize(consoleLogging=True):
 
         ANIMESUPPORT = False
         USE_ANIDB = bool(check_setting_int(CFG, 'ANIDB', 'use_anidb', 0))
-        ANIDB_USERNAME = check_setting_str(CFG, 'ANIDB', 'anidb_username', '')
-        ANIDB_PASSWORD = check_setting_str(CFG, 'ANIDB', 'anidb_password', '')
+        ANIDB_USERNAME = check_setting_str(CFG, 'ANIDB', 'anidb_username', '', censor_log=True)
+        ANIDB_PASSWORD = check_setting_str(CFG, 'ANIDB', 'anidb_password', '', censor_log=True)
         ANIDB_USE_MYLIST = bool(check_setting_int(CFG, 'ANIDB', 'anidb_use_mylist', 0))
 
         ANIME_SPLIT_HOME = bool(check_setting_int(CFG, 'ANIME', 'anime_split_home', 0))
 
-        METADATA_XBMC = check_setting_str(CFG, 'General', 'metadata_xbmc', '0|0|0|0|0|0|0|0|0|0')
-        METADATA_XBMC_12PLUS = check_setting_str(CFG, 'General', 'metadata_xbmc_12plus', '0|0|0|0|0|0|0|0|0|0')
+        METADATA_KODI = check_setting_str(CFG, 'General', 'metadata_kodi', '0|0|0|0|0|0|0|0|0|0')
+        METADATA_KODI_12PLUS = check_setting_str(CFG, 'General', 'metadata_kodi_12plus', '0|0|0|0|0|0|0|0|0|0')
         METADATA_MEDIABROWSER = check_setting_str(CFG, 'General', 'metadata_mediabrowser', '0|0|0|0|0|0|0|0|0|0')
         METADATA_PS3 = check_setting_str(CFG, 'General', 'metadata_ps3', '0|0|0|0|0|0|0|0|0|0')
         METADATA_WDTV = check_setting_str(CFG, 'General', 'metadata_wdtv', '0|0|0|0|0|0|0|0|0|0')
@@ -995,22 +1011,22 @@ def initialize(consoleLogging=True):
                                                                 curTorrentProvider.getID(), 0))
             if hasattr(curTorrentProvider, 'api_key'):
                 curTorrentProvider.api_key = check_setting_str(CFG, curTorrentProvider.getID().upper(),
-                                                               curTorrentProvider.getID() + '_api_key', '')
+                                                               curTorrentProvider.getID() + '_api_key', '', censor_log=True)
             if hasattr(curTorrentProvider, 'hash'):
                 curTorrentProvider.hash = check_setting_str(CFG, curTorrentProvider.getID().upper(),
-                                                            curTorrentProvider.getID() + '_hash', '')
+                                                            curTorrentProvider.getID() + '_hash', '', censor_log=True)
             if hasattr(curTorrentProvider, 'digest'):
                 curTorrentProvider.digest = check_setting_str(CFG, curTorrentProvider.getID().upper(),
-                                                              curTorrentProvider.getID() + '_digest', '')
+                                                              curTorrentProvider.getID() + '_digest', '', censor_log=True)
             if hasattr(curTorrentProvider, 'username'):
                 curTorrentProvider.username = check_setting_str(CFG, curTorrentProvider.getID().upper(),
-                                                                curTorrentProvider.getID() + '_username', '')
+                                                                curTorrentProvider.getID() + '_username', '', censor_log=True)
             if hasattr(curTorrentProvider, 'password'):
                 curTorrentProvider.password = check_setting_str(CFG, curTorrentProvider.getID().upper(),
-                                                                curTorrentProvider.getID() + '_password', '')
+                                                                curTorrentProvider.getID() + '_password', '', censor_log=True)
             if hasattr(curTorrentProvider, 'passkey'):
                 curTorrentProvider.passkey = check_setting_str(CFG, curTorrentProvider.getID().upper(),
-                                                               curTorrentProvider.getID() + '_passkey', '')
+                                                               curTorrentProvider.getID() + '_passkey', '', censor_log=True)
             if hasattr(curTorrentProvider, 'proxy'):
                 curTorrentProvider.proxy.enabled = bool(check_setting_int(CFG, curTorrentProvider.getID().upper(),
                                                                           curTorrentProvider.getID() + '_proxy', 0))
@@ -1060,10 +1076,10 @@ def initialize(consoleLogging=True):
                 check_setting_int(CFG, curNzbProvider.getID().upper(), curNzbProvider.getID(), 0))
             if hasattr(curNzbProvider, 'api_key'):
                 curNzbProvider.api_key = check_setting_str(CFG, curNzbProvider.getID().upper(),
-                                                           curNzbProvider.getID() + '_api_key', '')
+                                                           curNzbProvider.getID() + '_api_key', '', censor_log=True)
             if hasattr(curNzbProvider, 'username'):
                 curNzbProvider.username = check_setting_str(CFG, curNzbProvider.getID().upper(),
-                                                            curNzbProvider.getID() + '_username', '')
+                                                            curNzbProvider.getID() + '_username', '', censor_log=True)
             if hasattr(curNzbProvider, 'search_mode'):
                 curNzbProvider.search_mode = check_setting_str(CFG, curNzbProvider.getID().upper(),
                                                                curNzbProvider.getID() + '_search_mode',
@@ -1086,9 +1102,6 @@ def initialize(consoleLogging=True):
             logger.log(u"Unable to find '" + CONFIG_FILE + "', all settings will be default!", logger.DEBUG)
             save_config()
 
-        # start up all the threads
-        logger.sb_log_instance.initLogging(consoleLogging=consoleLogging)
-
         # initialize the main SB database
         myDB = db.DBConnection()
         db.upgradeDatabase(myDB, mainDB.InitialSchema)
@@ -1111,14 +1124,14 @@ def initialize(consoleLogging=True):
 
         # initialize metadata_providers
         metadata_provider_dict = metadata.get_metadata_generator_dict()
-        for cur_metadata_tuple in [(METADATA_XBMC, metadata.xbmc),
-                                   (METADATA_XBMC_12PLUS, metadata.xbmc_12plus),
+        for cur_metadata_tuple in [(METADATA_KODI, metadata.kodi),
+                                   (METADATA_KODI_12PLUS, metadata.kodi_12plus),
                                    (METADATA_MEDIABROWSER, metadata.mediabrowser),
                                    (METADATA_PS3, metadata.ps3),
                                    (METADATA_WDTV, metadata.wdtv),
                                    (METADATA_TIVO, metadata.tivo),
                                    (METADATA_MEDE8ER, metadata.mede8er),
-        ]:
+                                   ]:
             (cur_metadata_config, cur_metadata_class) = cur_metadata_tuple
             tmp_provider = cur_metadata_class.metadata_class()
             tmp_provider.set_config(cur_metadata_config)
@@ -1388,6 +1401,7 @@ def save_config():
 
     # For passwords you must include the word `password` in the item_name and add `helpers.encrypt(ITEM_NAME, ENCRYPTION_VERSION)` in save_config()
     new_config['General'] = {}
+    new_config['General']['git_reset'] = int(GIT_RESET)
     new_config['General']['branch'] = BRANCH
     new_config['General']['git_remote'] = GIT_REMOTE
     new_config['General']['git_remote_url'] = GIT_REMOTE_URL
@@ -1425,6 +1439,7 @@ def save_config():
     new_config['General']['backlog_frequency'] = int(BACKLOG_FREQUENCY)
     new_config['General']['update_frequency'] = int(UPDATE_FREQUENCY)
     new_config['General']['download_propers'] = int(DOWNLOAD_PROPERS)
+    new_config['General']['randomize_providers'] = int(RANDOMIZE_PROVIDERS)
     new_config['General']['check_propers_interval'] = CHECK_PROPERS_INTERVAL
     new_config['General']['allow_high_priority'] = int(ALLOW_HIGH_PRIORITY)
     new_config['General']['dailysearch_startup'] = int(DAILYSEARCH_STARTUP)
@@ -1461,8 +1476,8 @@ def save_config():
     new_config['General']['proxy_indexers'] = int(PROXY_INDEXERS)
 
     new_config['General']['use_listview'] = int(USE_LISTVIEW)
-    new_config['General']['metadata_xbmc'] = METADATA_XBMC
-    new_config['General']['metadata_xbmc_12plus'] = METADATA_XBMC_12PLUS
+    new_config['General']['metadata_kodi'] = METADATA_KODI
+    new_config['General']['metadata_kodi_12plus'] = METADATA_KODI_12PLUS
     new_config['General']['metadata_mediabrowser'] = METADATA_MEDIABROWSER
     new_config['General']['metadata_ps3'] = METADATA_PS3
     new_config['General']['metadata_wdtv'] = METADATA_WDTV
@@ -1620,18 +1635,18 @@ def save_config():
     new_config['TORRENT']['torrent_label_anime'] = TORRENT_LABEL_ANIME
     new_config['TORRENT']['torrent_verify_cert'] = int(TORRENT_VERIFY_CERT)
 
-    new_config['XBMC'] = {}
-    new_config['XBMC']['use_xbmc'] = int(USE_XBMC)
-    new_config['XBMC']['xbmc_always_on'] = int(XBMC_ALWAYS_ON)
-    new_config['XBMC']['xbmc_notify_onsnatch'] = int(XBMC_NOTIFY_ONSNATCH)
-    new_config['XBMC']['xbmc_notify_ondownload'] = int(XBMC_NOTIFY_ONDOWNLOAD)
-    new_config['XBMC']['xbmc_notify_onsubtitledownload'] = int(XBMC_NOTIFY_ONSUBTITLEDOWNLOAD)
-    new_config['XBMC']['xbmc_update_library'] = int(XBMC_UPDATE_LIBRARY)
-    new_config['XBMC']['xbmc_update_full'] = int(XBMC_UPDATE_FULL)
-    new_config['XBMC']['xbmc_update_onlyfirst'] = int(XBMC_UPDATE_ONLYFIRST)
-    new_config['XBMC']['xbmc_host'] = XBMC_HOST
-    new_config['XBMC']['xbmc_username'] = XBMC_USERNAME
-    new_config['XBMC']['xbmc_password'] = helpers.encrypt(XBMC_PASSWORD, ENCRYPTION_VERSION)
+    new_config['KODI'] = {}
+    new_config['KODI']['use_kodi'] = int(USE_KODI)
+    new_config['KODI']['kodi_always_on'] = int(KODI_ALWAYS_ON)
+    new_config['KODI']['kodi_notify_onsnatch'] = int(KODI_NOTIFY_ONSNATCH)
+    new_config['KODI']['kodi_notify_ondownload'] = int(KODI_NOTIFY_ONDOWNLOAD)
+    new_config['KODI']['kodi_notify_onsubtitledownload'] = int(KODI_NOTIFY_ONSUBTITLEDOWNLOAD)
+    new_config['KODI']['kodi_update_library'] = int(KODI_UPDATE_LIBRARY)
+    new_config['KODI']['kodi_update_full'] = int(KODI_UPDATE_FULL)
+    new_config['KODI']['kodi_update_onlyfirst'] = int(KODI_UPDATE_ONLYFIRST)
+    new_config['KODI']['kodi_host'] = KODI_HOST
+    new_config['KODI']['kodi_username'] = KODI_USERNAME
+    new_config['KODI']['kodi_password'] = helpers.encrypt(KODI_PASSWORD, ENCRYPTION_VERSION)
 
     new_config['Plex'] = {}
     new_config['Plex']['use_plex'] = int(USE_PLEX)
@@ -1812,6 +1827,7 @@ def save_config():
     new_config['Subtitles']['subtitles_default'] = int(SUBTITLES_DEFAULT)
     new_config['Subtitles']['subtitles_history'] = int(SUBTITLES_HISTORY)
     new_config['Subtitles']['subtitles_finder_frequency'] = int(SUBTITLES_FINDER_FREQUENCY)
+    new_config['Subtitles']['subtitles_multi'] = int(SUBTITLES_MULTI)
 
     new_config['FailedDownloads'] = {}
     new_config['FailedDownloads']['use_failed_downloads'] = int(USE_FAILED_DOWNLOADS)
@@ -1829,13 +1845,12 @@ def save_config():
     new_config.write()
 
 
-def launchBrowser(startPort=None):
+def launchBrowser(protocol='http', startPort=None, web_root='/'):
     if not startPort:
         startPort = WEB_PORT
-    if ENABLE_HTTPS:
-        browserURL = 'https://localhost:%d%s' % (startPort, WEB_ROOT)
-    else:
-        browserURL = 'http://localhost:%d%s' % (startPort, WEB_ROOT)
+
+    browserURL = '%s://localhost:%d%s/home/' % (protocol, startPort, web_root)
+
     try:
         webbrowser.open(browserURL, 2, 1)
     except:
diff --git a/sickbeard/browser.py b/sickbeard/browser.py
index 72d47bd067a69864231a4672bba7b95fd4d7f5b7..cec457eff2e9d422f971b814e2eb798445e0851c 100644
--- a/sickbeard/browser.py
+++ b/sickbeard/browser.py
@@ -19,7 +19,6 @@
 import os
 import string
 
-from tornado.httputil import HTTPHeaders
 from tornado.web import RequestHandler
 from sickbeard import encodingKludge as ek
 from sickbeard import logger
@@ -104,15 +103,4 @@ def foldersAtPath(path, includeParent=False, includeFiles=False):
         entries.append({'name': "..", 'path': parentPath})
     entries.extend(fileList)
 
-    return entries
-
-
-class WebFileBrowser(RequestHandler):
-    def index(self, path='', includeFiles=False, *args, **kwargs):
-        self.set_header("Content-Type", "application/json")
-        return json.dumps(foldersAtPath(path, True, bool(int(includeFiles))))
-
-    def complete(self, term, includeFiles=0):
-        self.set_header("Content-Type", "application/json")
-        paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term), includeFiles=bool(int(includeFiles))) if 'path' in entry]
-        return json.dumps(paths)
\ No newline at end of file
+    return entries
\ No newline at end of file
diff --git a/sickbeard/classes.py b/sickbeard/classes.py
index 962df06e51525448d90ef9be4380c18a0fc889aa..7036b0ee0cd0ebed2486fd1fd0c926b1cd200170 100644
--- a/sickbeard/classes.py
+++ b/sickbeard/classes.py
@@ -107,7 +107,7 @@ class SearchResult:
 
     def __str__(self):
 
-        if self.provider == None:
+        if self.provider is None:
             return "Invalid provider, unable to print self"
 
         myString = self.provider.name + " @ " + self.url + "\n"
diff --git a/sickbeard/common.py b/sickbeard/common.py
index d646617f087372cfd11b86e4e2bec51a901bf53d..24a31cc8cde60225d8ab9e3781cd160e72cd6bb0 100644
--- a/sickbeard/common.py
+++ b/sickbeard/common.py
@@ -176,6 +176,8 @@ class Quality:
         """
         Return The quality from the scene episode File 
         """
+        if not name:
+            return Quality.UNKNOWN
 
         name = os.path.basename(name)
 
diff --git a/sickbeard/config.py b/sickbeard/config.py
index b8e2881479cf4c9ab350103b6bdfb7a084d173f5..a1927bbf77ed2cee2571f484a9d357b530ff62bf 100644
--- a/sickbeard/config.py
+++ b/sickbeard/config.py
@@ -89,7 +89,7 @@ def change_LOG_DIR(log_dir, web_log):
             sickbeard.ACTUAL_LOG_DIR = os.path.normpath(log_dir)
             sickbeard.LOG_DIR = abs_log_dir
 
-            logger.sb_log_instance.initLogging()
+            logger.initLogging()
             logger.log(u"Initialized new log file in " + sickbeard.LOG_DIR)
             log_dir_changed = True
 
@@ -369,9 +369,11 @@ def minimax(val, default, low, high):
 ################################################################################
 # Check_setting_int                                                            #
 ################################################################################
-def check_setting_int(config, cfg_name, item_name, def_val):
+def check_setting_int(config, cfg_name, item_name, def_val, silent=True):
     try:
         my_val = int(config[cfg_name][item_name])
+        if str(my_val) == str(None):
+            raise
     except:
         my_val = def_val
         try:
@@ -379,16 +381,21 @@ def check_setting_int(config, cfg_name, item_name, def_val):
         except:
             config[cfg_name] = {}
             config[cfg_name][item_name] = my_val
-    logger.log(item_name + " -> " + str(my_val), logger.DEBUG)
+
+    if not silent:
+        logger.log(item_name + " -> " + str(my_val), logger.DEBUG)
+
     return my_val
 
 
 ################################################################################
 # Check_setting_float                                                          #
 ################################################################################
-def check_setting_float(config, cfg_name, item_name, def_val):
+def check_setting_float(config, cfg_name, item_name, def_val, silent=True):
     try:
         my_val = float(config[cfg_name][item_name])
+        if str(my_val) == str(None):
+            raise
     except:
         my_val = def_val
         try:
@@ -397,14 +404,16 @@ def check_setting_float(config, cfg_name, item_name, def_val):
             config[cfg_name] = {}
             config[cfg_name][item_name] = my_val
 
-    logger.log(item_name + " -> " + str(my_val), logger.DEBUG)
+    if not silent:
+        logger.log(item_name + " -> " + str(my_val), logger.DEBUG)
+
     return my_val
 
 
 ################################################################################
 # Check_setting_str                                                            #
 ################################################################################
-def check_setting_str(config, cfg_name, item_name, def_val, log=True):
+def check_setting_str(config, cfg_name, item_name, def_val, silent=True, censor_log=False):
     # For passwords you must include the word `password` in the item_name and add `helpers.encrypt(ITEM_NAME, ENCRYPTION_VERSION)` in save_config()
     if bool(item_name.find('password') + 1):
         log = False
@@ -414,6 +423,8 @@ def check_setting_str(config, cfg_name, item_name, def_val, log=True):
 
     try:
         my_val = helpers.decrypt(config[cfg_name][item_name], encryption_version)
+        if str(my_val) == str(None):
+            raise
     except:
         my_val = def_val
         try:
@@ -422,10 +433,11 @@ def check_setting_str(config, cfg_name, item_name, def_val, log=True):
             config[cfg_name] = {}
             config[cfg_name][item_name] = helpers.encrypt(my_val, encryption_version)
 
-    if log:
+    if censor_log or (cfg_name, item_name) in logger.censoredItems.items():
+        logger.censoredItems[cfg_name, item_name] = my_val
+
+    if not silent:
         logger.log(item_name + " -> " + str(my_val), logger.DEBUG)
-    else:
-        logger.log(item_name + " -> ******", logger.DEBUG)
 
     return my_val
 
@@ -661,11 +673,11 @@ class ConfigMigrator():
         new format: 0|0|0|0|0|0|0|0|0|0 -- 10 places
 
         Drop the use of use_banner option.
-        Migrate the poster override to just using the banner option (applies to xbmc only).
+        Migrate the poster override to just using the banner option (applies to kodi only).
         """
 
-        metadata_xbmc = check_setting_str(self.config_obj, 'General', 'metadata_xbmc', '0|0|0|0|0|0')
-        metadata_xbmc_12plus = check_setting_str(self.config_obj, 'General', 'metadata_xbmc_12plus', '0|0|0|0|0|0')
+        metadata_kodi = check_setting_str(self.config_obj, 'General', 'metadata_kodi', '0|0|0|0|0|0')
+        metadata_kodi_12plus = check_setting_str(self.config_obj, 'General', 'metadata_kodi_12plus', '0|0|0|0|0|0')
         metadata_mediabrowser = check_setting_str(self.config_obj, 'General', 'metadata_mediabrowser', '0|0|0|0|0|0')
         metadata_ps3 = check_setting_str(self.config_obj, 'General', 'metadata_ps3', '0|0|0|0|0|0')
         metadata_wdtv = check_setting_str(self.config_obj, 'General', 'metadata_wdtv', '0|0|0|0|0|0')
@@ -686,7 +698,7 @@ class ConfigMigrator():
                 # swap show fanart, show poster
                 cur_metadata[3], cur_metadata[2] = cur_metadata[2], cur_metadata[3]
                 # if user was using use_banner to override the poster, instead enable the banner option and deactivate poster
-                if metadata_name == 'XBMC' and use_banner:
+                if metadata_name == 'KODI' and use_banner:
                     cur_metadata[4], cur_metadata[3] = cur_metadata[3], '0'
                 # write new format
                 metadata = '|'.join(cur_metadata)
@@ -705,8 +717,8 @@ class ConfigMigrator():
 
             return metadata
 
-        sickbeard.METADATA_XBMC = _migrate_metadata(metadata_xbmc, 'XBMC', use_banner)
-        sickbeard.METADATA_XBMC_12PLUS = _migrate_metadata(metadata_xbmc_12plus, 'XBMC 12+', use_banner)
+        sickbeard.METADATA_KODI = _migrate_metadata(metadata_kodi, 'KODI', use_banner)
+        sickbeard.METADATA_KODI_12PLUS = _migrate_metadata(metadata_kodi_12plus, 'KODI 12+', use_banner)
         sickbeard.METADATA_MEDIABROWSER = _migrate_metadata(metadata_mediabrowser, 'MediaBrowser', use_banner)
         sickbeard.METADATA_PS3 = _migrate_metadata(metadata_ps3, 'PS3', use_banner)
         sickbeard.METADATA_WDTV = _migrate_metadata(metadata_wdtv, 'WDTV', use_banner)
diff --git a/sickbeard/dailysearcher.py b/sickbeard/dailysearcher.py
index 3e91d46e8082476d74cc5e048ab8c68d172bad75..d7e84b40227a15055ee03fbc54bd53ea2cea4f3d 100644
--- a/sickbeard/dailysearcher.py
+++ b/sickbeard/dailysearcher.py
@@ -74,7 +74,9 @@ class DailySearcher():
                 continue
 
             try:
-                end_time = network_timezones.parse_date_time(sqlEp['airdate'], show.airs, show.network) + datetime.timedelta(minutes=helpers.tryInt(show.runtime, 60))
+                end_time = network_timezones.parse_date_time(sqlEp['airdate'], show.airs,
+                                                             show.network) + datetime.timedelta(
+                    minutes=helpers.tryInt(show.runtime, 60))
                 # filter out any episodes that haven't aried yet
                 if end_time > curTime:
                     continue
diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py
index 9f969c81dc84853d84f747caf913a3c7f6ebac13..66ba94bd953e61e0abd3aa670f2038aa52ea5a64 100644
--- a/sickbeard/databases/mainDB.py
+++ b/sickbeard/databases/mainDB.py
@@ -36,6 +36,7 @@ class MainSanityCheck(db.DBSanityCheck):
         self.fix_duplicate_episodes()
         self.fix_orphan_episodes()
         self.fix_unaired_episodes()
+        self.fix_tvrage_show_statues()
 
     def fix_duplicate_shows(self, column='indexer_id'):
 
@@ -142,6 +143,25 @@ class MainSanityCheck(db.DBSanityCheck):
         else:
             logger.log(u"No UNAIRED episodes, check passed")
 
+    def fix_tvrage_show_statues(self):
+        status_map = {
+            'returning series': 'Continuing',
+            'canceled/ended': 'Ended',
+            'tbd/on the bubble': 'Continuing',
+            'in development': 'Continuing',
+            'new series': 'Continuing',
+            'never aired': 'Ended',
+            'final season': 'Continuing',
+            'on hiatus': 'Continuing',
+            'pilot ordered': 'Continuing',
+            'pilot rejected': 'Ended',
+            'canceled': 'Ended',
+            'ended': 'Ended',
+            '': 'Unknown',
+        }
+
+        for old_status, new_status in status_map.items():
+            self.connection.action("UPDATE tv_shows SET status = ? WHERE LOWER(status) = ?", [new_status, old_status])
 
 def backupDatabase(version):
     logger.log(u"Backing up database before upgrade")
@@ -417,7 +437,7 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders):
              common.Quality.UNKNOWN], [])
 
         # update qualities (including templates)
-        logger.log(u"[1/4] Updating pre-defined templates and the quality for each show...", logger.MESSAGE)
+        logger.log(u"[1/4] Updating pre-defined templates and the quality for each show...", logger.INFO)
         cl = []
         shows = self.connection.select("SELECT * FROM tv_shows")
         for cur_show in shows:
@@ -431,7 +451,7 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders):
         self.connection.mass_action(cl)
 
         # update status that are are within the old hdwebdl (1<<3 which is 8) and better -- exclude unknown (1<<15 which is 32768)
-        logger.log(u"[2/4] Updating the status for the episodes within each show...", logger.MESSAGE)
+        logger.log(u"[2/4] Updating the status for the episodes within each show...", logger.INFO)
         cl = []
         episodes = self.connection.select("SELECT * FROM tv_episodes WHERE status < 3276800 AND status >= 800")
         for cur_episode in episodes:
@@ -442,7 +462,7 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders):
         # make two seperate passes through the history since snatched and downloaded (action & quality) may not always coordinate together
 
         # update previous history so it shows the correct action
-        logger.log(u"[3/4] Updating history to reflect the correct action...", logger.MESSAGE)
+        logger.log(u"[3/4] Updating history to reflect the correct action...", logger.INFO)
         cl = []
         historyAction = self.connection.select("SELECT * FROM history WHERE action < 3276800 AND action >= 800")
         for cur_entry in historyAction:
@@ -451,7 +471,7 @@ class Add1080pAndRawHDQualities(RenameSeasonFolders):
         self.connection.mass_action(cl)
 
         # update previous history so it shows the correct quality
-        logger.log(u"[4/4] Updating history to reflect the correct quality...", logger.MESSAGE)
+        logger.log(u"[4/4] Updating history to reflect the correct quality...", logger.INFO)
         cl = []
         historyQuality = self.connection.select("SELECT * FROM history WHERE quality < 32768 AND quality >= 8")
         for cur_entry in historyQuality:
@@ -725,7 +745,7 @@ class ConvertIndexerToInteger(AddSceneNumbering):
         backupDatabase(28)
 
         cl = []
-        logger.log(u"Converting Indexer to Integer ...", logger.MESSAGE)
+        logger.log(u"Converting Indexer to Integer ...", logger.INFO)
         cl.append(["UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?", ["1", "tvdb"]])
         cl.append(["UPDATE tv_shows SET indexer = ? WHERE LOWER(indexer) = ?", ["2", "tvrage"]])
         cl.append(["UPDATE tv_episodes SET indexer = ? WHERE LOWER(indexer) = ?", ["1", "tvdb"]])
@@ -771,7 +791,7 @@ class AddSportsOption(AddRequireAndIgnoreWords):
 
         if self.hasColumn("tv_shows", "air_by_date") and self.hasColumn("tv_shows", "sports"):
             # update sports column
-            logger.log(u"[4/4] Updating tv_shows to reflect the correct sports value...", logger.MESSAGE)
+            logger.log(u"[4/4] Updating tv_shows to reflect the correct sports value...", logger.INFO)
             cl = []
             historyQuality = self.connection.select(
                 "SELECT * FROM tv_shows WHERE LOWER(classification) = 'sports' AND air_by_date = 1 AND sports = 0")
diff --git a/sickbeard/db.py b/sickbeard/db.py
index b629a7d118df4af60a4a95d97aa021bd749b178c..1d633b08ddc3bf673e3700f414624387d5715fc2 100644
--- a/sickbeard/db.py
+++ b/sickbeard/db.py
@@ -30,8 +30,8 @@ from sickbeard import encodingKludge as ek
 from sickbeard import logger
 from sickbeard.exceptions import ex
 
-db_lock = threading.Lock()
-
+db_cons = {}
+db_locks = {}
 
 def dbFilename(filename="sickbeard.db", suffix=None):
     """
@@ -45,63 +45,60 @@ def dbFilename(filename="sickbeard.db", suffix=None):
         filename = "%s.%s" % (filename, suffix)
     return ek.ek(os.path.join, sickbeard.DATA_DIR, filename)
 
-
 class DBConnection(object):
     def __init__(self, filename="sickbeard.db", suffix=None, row_type=None):
 
         self.filename = filename
         self.suffix = suffix
         self.row_type = row_type
-        self.connection = None
 
         try:
-            self.reconnect()
+            if self.filename not in db_cons:
+                db_locks[self.filename] = threading.Lock()
+
+                self.connection = sqlite3.connect(dbFilename(self.filename, self.suffix), 20, check_same_thread=False)
+                self.connection.text_factory = self._unicode_text_factory
+                self.connection.isolation_level = None
+
+                db_cons[self.filename] = self.connection
+            else:
+                self.connection = db_cons[self.filename]
+
+            if self.row_type == "dict":
+                self.connection.row_factory = self._dict_factory
+            else:
+                self.connection.row_factory = sqlite3.Row
         except Exception as e:
             logger.log(u"DB error: " + ex(e), logger.ERROR)
             raise
 
-    def reconnect(self):
-        """Closes the existing database connection and re-opens it."""
-        self.close()
-        self.connection = sqlite3.connect(dbFilename(self.filename, self.suffix), 20, check_same_thread=False)
-        self.connection.isolation_level = None
-
-        if self.row_type == "dict":
-            self.connection.row_factory = self._dict_factory
-        else:
-            self.connection.row_factory = sqlite3.Row
-
-    def __del__(self):
-        self.close()
+    def _execute(self, query, args):
+        def convert(x):
+            if isinstance(x, basestring):
+                try:
+                    x = unicode(x).decode(sickbeard.SYS_ENCODING)
+                except:
+                    pass
+            return x
 
-    def _cursor(self):
-        """Returns the cursor; reconnects if disconnected."""
-        if self.connection is None: self.reconnect()
-        return self.connection.cursor()
+        try:
+            if not args:
+                return self.connection.cursor().execute(query)
+            # args = map(convert, args)
+            return self.connection.cursor().execute(query, args)
+        except Exception as e:
+            raise e
 
     def execute(self, query, args=None, fetchall=False, fetchone=False):
-        """Executes the given query, returning the lastrowid from the query."""
-        cursor = self._cursor()
-
         try:
             if fetchall:
-                return self._execute(cursor, query, args).fetchall()
+                return self._execute(query, args).fetchall()
             elif fetchone:
-                return self._execute(cursor, query, args).fetchone()
+                return self._execute(query, args).fetchone()
             else:
-                return self._execute(cursor, query, args)
-        finally:
-            cursor.close()
-
-    def _execute(self, cursor, query, args):
-        try:
-            if args == None:
-                return cursor.execute(query)
-            return cursor.execute(query, args)
-        except sqlite3.OperationalError as e:
-            logger.log(u"DB error: " + ex(e), logger.ERROR)
-            self.close()
-            raise
+                return self._execute(query, args)
+        except Exception as e:
+            raise e
 
     def checkDBVersion(self):
 
@@ -118,18 +115,14 @@ class DBConnection(object):
         else:
             return 0
 
-    def mass_action(self, querylist, logTransaction=False, fetchall=False):
-
-        with db_lock:
-            # remove None types
-            querylist = [i for i in querylist if i != None]
-
-            if querylist == None:
-                return
+    def mass_action(self, querylist=[], logTransaction=False, fetchall=False):
+        # remove None types
+        querylist = [i for i in querylist if i is not None]
 
-            sqlResult = []
-            attempt = 0
+        sqlResult = []
+        attempt = 0
 
+        with db_locks[self.filename]:
             while attempt < 5:
                 try:
                     for qu in querylist:
@@ -169,15 +162,13 @@ class DBConnection(object):
             return sqlResult
 
     def action(self, query, args=None, fetchall=False, fetchone=False):
+        if query == None:
+            return
 
-        with db_lock:
-
-            if query == None:
-                return
-
-            sqlResult = None
-            attempt = 0
+        sqlResult = None
+        attempt = 0
 
+        with db_locks[self.filename]:
             while attempt < 5:
                 try:
                     if args == None:
@@ -240,15 +231,15 @@ class DBConnection(object):
             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
         sqlResult = self.select("PRAGMA table_info(%s)" % tableName)
         columns = {}
         for column in sqlResult:
             columns[column['name']] = {'type': column['type']}
         return columns
 
-    # http://stackoverflow.com/questions/3300464/how-can-i-get-dict-from-sqlite-query
+    def _unicode_text_factory(self, x):
+        return unicode(x, 'utf-8')
+
     def _dict_factory(self, cursor, row):
         d = {}
         for idx, col in enumerate(cursor.description):
@@ -265,16 +256,9 @@ class DBConnection(object):
         self.action("ALTER TABLE %s ADD %s %s" % (table, column, type))
         self.action("UPDATE %s SET %s = ?" % (table, column), (default,))
 
-    def close(self):
-        """Close database connection"""
-        if getattr(self, "connection", None) is not None:
-            self.connection.close()
-        self.connection = None
-
 def sanityCheckDatabase(connection, sanity_check):
     sanity_check(connection).check()
 
-
 class DBSanityCheck(object):
     def __init__(self, connection):
         self.connection = connection
@@ -288,7 +272,7 @@ class DBSanityCheck(object):
 # ===============
 
 def upgradeDatabase(connection, schema):
-    logger.log(u"Checking database structure...", logger.MESSAGE)
+    logger.log(u"Checking database structure...", logger.INFO)
     _processUpgrade(connection, schema)
 
 
@@ -309,7 +293,7 @@ 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)
+        logger.log(u"Database upgrade required: " + prettyName(upgradeClass.__name__), logger.INFO)
         try:
             instance.execute()
         except sqlite3.DatabaseError, e:
diff --git a/sickbeard/encodingKludge.py b/sickbeard/encodingKludge.py
index a3d945412ba0cbfb1c3ec6c15776206b4441c697..0c1828042022cb0a7f183011a83d5bcb53cfed14 100644
--- a/sickbeard/encodingKludge.py
+++ b/sickbeard/encodingKludge.py
@@ -18,59 +18,46 @@
 
 import os
 import traceback
-
+import re
 import sickbeard
-from sickbeard import logger
-
 import six
 import chardet
+import unicodedata
 
+from string import ascii_letters, digits
+from sickbeard import logger
+from unidecode import unidecode
 
-# This module tries to deal with the apparently random behavior of python when dealing with unicode <-> utf-8
-# encodings. It tries to just use unicode, but if that fails then it tries forcing it to utf-8. Any functions
-# which return something should always return unicode.
 
-def toUnicode(x):
+def _toUnicode(x):
     try:
-        if isinstance(x, unicode):
-            return x
-        else:
-            try:
-                return six.text_type(x)
-            except:
-                try:
-                    if chardet.detect(x).get('encoding') == 'utf-8':
-                        return x.decode('utf-8')
-                    if isinstance(x, str):
-                        try:
-                            return x.decode(sickbeard.SYS_ENCODING)
-                        except UnicodeDecodeError:
-                            raise
-                    return x
-                except:
-                    raise
-    except:
-        logger.log('Unable to decode value "%s..." : %s ' % (repr(x)[:20], traceback.format_exc()), logger.WARNING)
-        ascii_text = str(x).encode('string_escape')
-        return toUnicode(ascii_text)
+        if not isinstance(x, unicode):
+            if chardet.detect(x).get('encoding') == 'utf-8':
+                x = x.decode('utf-8')
+            elif isinstance(x, str):
+                x = x.decode(sickbeard.SYS_ENCODING)
+    finally:
+        return x
 
 def ss(x):
-    u_x = toUnicode(x)
+    x = _toUnicode(x)
 
     try:
-        return u_x.encode(sickbeard.SYS_ENCODING)
-    except Exception as e:
-        logger.log('Failed ss encoding char, force UTF8: %s' % e, logger.WARNING)
         try:
-            return u_x.encode(sickbeard.SYS_ENCODING, 'replace')
+            try:
+                x = x.encode(sickbeard.SYS_ENCODING)
+            except:
+                x = x.encode(sickbeard.SYS_ENCODING, 'ignore')
         except:
-            return u_x.encode('utf-8', 'replace')
+            x = x.encode('utf-8', 'ignore')
+    finally:
+        return x
 
 def fixListEncodings(x):
     if not isinstance(x, (list, tuple)):
         return x
     else:
-        return filter(lambda x: x != None, map(toUnicode, x))
+        return filter(lambda x: x != None, map(_toUnicode, x))
 
 
 def ek(func, *args, **kwargs):
@@ -82,6 +69,6 @@ def ek(func, *args, **kwargs):
     if isinstance(result, (list, tuple)):
         return fixListEncodings(result)
     elif isinstance(result, str):
-        return toUnicode(result)
+        return _toUnicode(result)
     else:
-        return result
+        return result
\ No newline at end of file
diff --git a/sickbeard/exceptions.py b/sickbeard/exceptions.py
index db3b5735e0b96aa9c53a6bf4882ea5dd91d879cd..1e9114e8ffa0a20db35dd6961818b805c83143ca 100644
--- a/sickbeard/exceptions.py
+++ b/sickbeard/exceptions.py
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License
 # along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
 
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
 
 def ex(e):
     """
@@ -32,11 +32,11 @@ def ex(e):
 
         if arg is not None:
             if isinstance(arg, (str, unicode)):
-                fixed_arg = toUnicode(arg)
+                fixed_arg = ek.ss(arg)
 
             else:
                 try:
-                    fixed_arg = u"error " + toUnicode(str(arg))
+                    fixed_arg = u"error " + ek.ss(str(arg))
 
                 except:
                     fixed_arg = None
diff --git a/sickbeard/failedProcessor.py b/sickbeard/failedProcessor.py
index b66f9be2cec81439d97192c9ef80e5627980c698..6c1245bb893281bd8f4ea77e2e2fc849a113d4f2 100644
--- a/sickbeard/failedProcessor.py
+++ b/sickbeard/failedProcessor.py
@@ -72,7 +72,7 @@ class FailedProcessor(object):
 
         return True
 
-    def _log(self, message, level=logger.MESSAGE):
+    def _log(self, message, level=logger.INFO):
         """Log to regular logfile and save for return for PP script log"""
         logger.log(message, level)
         self.log += message + "\n"
\ No newline at end of file
diff --git a/sickbeard/failed_history.py b/sickbeard/failed_history.py
index 0fc1484b2909c64e221be0de9eb069b413d7c11b..0fda51eabb9fe1fab1ada2f3de9bf0f0084fd292 100644
--- a/sickbeard/failed_history.py
+++ b/sickbeard/failed_history.py
@@ -26,7 +26,7 @@ from sickbeard.exceptions import ex, EpisodeNotFoundException
 from sickbeard.history import dateFormat
 from sickbeard.common import Quality
 from sickbeard.common import WANTED, FAILED
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
 
 def prepareFailedName(release):
     """Standardizes release name for failed DB"""
@@ -36,7 +36,7 @@ def prepareFailedName(release):
         fixed = fixed.rpartition(".")[0]
 
     fixed = re.sub("[\.\-\+\ ]", "_", fixed)
-    fixed = toUnicode(fixed)
+    fixed = ek.ss(fixed)
 
     return fixed
 
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index 3e8847c86a7ba5c8a7531babc1f9278b224078e3..ee2015d4f75219ccd6904bce82bfc86437427e54 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -20,6 +20,7 @@ from __future__ import with_statement
 import getpass
 
 import os
+import random
 import re
 import shutil
 import socket
@@ -42,18 +43,6 @@ import adba
 import requests
 import requests.exceptions
 
-try:
-    import json
-except ImportError:
-    from lib import simplejson as json
-
-try:
-    import xml.etree.cElementTree as etree
-except ImportError:
-    import elementtree.ElementTree as etree
-
-from xml.dom.minidom import Node
-
 from sickbeard.exceptions import MultipleShowObjectsException, ex
 from sickbeard import logger, classes
 from sickbeard.common import USER_AGENT, mediaExtensions, subtitleExtensions
@@ -658,12 +647,11 @@ def get_all_episodes_from_absolute_number(show, absolute_numbers, indexer_id=Non
         if not show and indexer_id:
             show = findCertainShow(sickbeard.showList, indexer_id)
 
-        if show:
-            for absolute_number in absolute_numbers:
-                ep = show.getEpisode(None, None, absolute_number=absolute_number)
-                if ep:
-                    episodes.append(ep.episode)
-                    season = ep.season  # this will always take the last found seson so eps that cross the season border are not handeled well
+        for absolute_number in absolute_numbers if show else []:
+            ep = show.getEpisode(None, None, absolute_number=absolute_number)
+            if ep:
+                episodes.append(ep.episode)
+                season = ep.season  # this will always take the last found seson so eps that cross the season border are not handeled well
 
     return (season, episodes)
 
@@ -732,79 +720,6 @@ def create_https_certificates(ssl_cert, ssl_key):
 
     return True
 
-
-if __name__ == '__main__':
-    import doctest
-
-    doctest.testmod()
-
-
-def parse_json(data):
-    """
-    Parse json data into a python object
-
-    data: data string containing json
-
-    Returns: parsed data as json or None
-    """
-
-    try:
-        parsedJSON = json.loads(data)
-    except ValueError, e:
-        logger.log(u"Error trying to decode json data. Error: " + ex(e), logger.DEBUG)
-        return None
-
-    return parsedJSON
-
-
-def parse_xml(data, del_xmlns=False):
-    """
-    Parse data into an xml elementtree.ElementTree
-
-    data: data string containing xml
-    del_xmlns: if True, removes xmlns namesspace from data before parsing
-
-    Returns: parsed data as elementtree or None
-    """
-
-    if del_xmlns:
-        data = re.sub(' xmlns="[^"]+"', '', data)
-
-    try:
-        parsedXML = etree.fromstring(data)
-    except Exception, e:
-        logger.log(u"Error trying to parse xml data. Error: " + ex(e), logger.DEBUG)
-        parsedXML = None
-
-    return parsedXML
-
-
-def get_xml_text(element, mini_dom=False):
-    """
-    Get all text inside a xml element
-
-    element: A xml element either created with elementtree.ElementTree or xml.dom.minidom
-    mini_dom: Default False use elementtree, True use minidom
-
-    Returns: text
-    """
-
-    text = ""
-
-    if mini_dom:
-        node = element
-        for child in node.childNodes:
-            if child.nodeType in (Node.CDATA_SECTION_NODE, Node.TEXT_NODE):
-                text += child.data
-    else:
-        if element is not None:
-            for child in [element] + element.findall('.//*'):
-                if child.text:
-                    text += child.text
-
-    return text.strip()
-
-
 def backupVersionedFile(old_file, version):
     numTries = 0
 
@@ -1187,7 +1102,7 @@ def mapIndexersToShow(showObj):
 
             try:
                 mapped_show = t[showObj.name]
-            except sickbeard.indexer_shownotfound:
+            except Exception:
                 logger.log(u"Unable to map " + sickbeard.indexerApi(showObj.indexer).name + "->" + sickbeard.indexerApi(
                     indexer).name + " for show: " + showObj.name + ", skipping it", logger.DEBUG)
                 continue
@@ -1396,32 +1311,6 @@ def clearCache(force=False):
                                            logger.WARNING)
                                 break
 
-def human(size):
-    """
-    format a size in bytes into a 'human' file size, e.g. bytes, KB, MB, GB, TB, PB
-    Note that bytes/KB will be reported in whole numbers but MB and above will have greater precision
-    e.g. 1 byte, 43 bytes, 443 KB, 4.3 MB, 4.43 GB, etc
-    """
-    if size == 1:
-        # because I really hate unnecessary plurals
-        return "1 byte"
-
-    suffixes_table = [('bytes', 0), ('KB', 0), ('MB', 1), ('GB', 2),('TB', 2), ('PB', 2)]
-
-    num = float(size)
-    for suffix, precision in suffixes_table:
-        if num < 1024.0:
-            break
-        num /= 1024.0
-
-    if precision == 0:
-        formatted_size = "%d" % num
-    else:
-        formatted_size = str(round(num, ndigits=precision))
-
-    return "%s %s" % (formatted_size, suffix)
-
-
 def get_size(start_path='.'):
 
     total_size = 0
@@ -1431,3 +1320,51 @@ def get_size(start_path='.'):
             total_size += ek.ek(os.path.getsize, fp)
     return total_size
 
+def generateApiKey():
+    """ Return a new randomized API_KEY
+    """
+
+    try:
+        from hashlib import md5
+    except ImportError:
+        from md5 import md5
+
+    # Create some values to seed md5
+    t = str(time.time())
+    r = str(random.random())
+
+    # Create the md5 instance and give it the current time
+    m = md5(t)
+
+    # Update the md5 instance with the random variable
+    m.update(r)
+
+    # Return a hex digest of the md5, eg 49f68a5c8493ec2c0bf489821c21fc3b
+    logger.log(u"New API generated")
+    return m.hexdigest()
+
+def pretty_filesize(file_bytes):
+    file_bytes = float(file_bytes)
+    if file_bytes >= 1099511627776:
+        terabytes = file_bytes / 1099511627776
+        size = '%.2f TB' % terabytes
+    elif file_bytes >= 1073741824:
+        gigabytes = file_bytes / 1073741824
+        size = '%.2f GB' % gigabytes
+    elif file_bytes >= 1048576:
+        megabytes = file_bytes / 1048576
+        size = '%.2f MB' % megabytes
+    elif file_bytes >= 1024:
+        kilobytes = file_bytes / 1024
+        size = '%.2f KB' % kilobytes
+    else:
+        size = '%.2f b' % file_bytes
+
+    return size
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
+
+def remove_article(text=''):
+    return re.sub(r'(?i)/^(?:(?:A(?!\s+to)n?)|The)\s(\w)', r'\1', text)
\ No newline at end of file
diff --git a/sickbeard/history.py b/sickbeard/history.py
index cb1a8486a68e47449c720189a016e23602c1f6cd..2df2590477ba97bff30202590549780b4e298834 100644
--- a/sickbeard/history.py
+++ b/sickbeard/history.py
@@ -20,7 +20,7 @@ import db
 import datetime
 
 from sickbeard.common import SNATCHED, SUBTITLED, FAILED, Quality
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
 
 
 dateFormat = "%Y%m%d%H%M%S"
@@ -28,7 +28,7 @@ dateFormat = "%Y%m%d%H%M%S"
 
 def _logHistoryItem(action, showid, season, episode, quality, resource, provider, version=-1):
     logDate = datetime.datetime.today().strftime(dateFormat)
-    resource = toUnicode(resource)
+    resource = ek.ss(resource)
 
     myDB = db.DBConnection()
     myDB.action(
diff --git a/sickbeard/logger.py b/sickbeard/logger.py
index 3b77a39435467e5be2ffaa0927df33373a8021ce..265959e0fc0254d420bee7e7b25f8c0e50b49b20 100644
--- a/sickbeard/logger.py
+++ b/sickbeard/logger.py
@@ -17,322 +17,129 @@
 # along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import with_statement
-
-import time
 import os
-import sys
-import threading
 
+import sys
 import logging
+import logging.handlers
+import threading
 
 import sickbeard
-
 from sickbeard import classes
 
-try:
-    from lib.send2trash import send2trash
-except ImportError:
-    pass
-
-
-# number of log files to keep
-NUM_LOGS = 3
-
-# log size in bytes
-LOG_SIZE = 10000000  # 10 megs
-
+# log levels
 ERROR = logging.ERROR
 WARNING = logging.WARNING
-MESSAGE = logging.INFO
+INFO = logging.INFO
 DEBUG = logging.DEBUG
 DB = 5
 
 reverseNames = {u'ERROR': ERROR,
                 u'WARNING': WARNING,
-                u'INFO': MESSAGE,
+                u'INFO': INFO,
                 u'DEBUG': DEBUG,
                 u'DB': DB}
 
-# send logging to null
+censoredItems = {}
+
 class NullHandler(logging.Handler):
     def emit(self, record):
         pass
 
-class SBRotatingLogHandler(object):
-    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.log_file_path = log_file
-        self.cur_handler = None
-
-        self.writes_since_check = 0
-
-        self.console_logging = False
-        self.log_lock = threading.Lock()
-
-    def __del__(self):
-        pass
-
-    def close_log(self, handler=None):
-        if not handler:
-            handler = self.cur_handler
-
-        if handler:
-            sb_logger = logging.getLogger('sickbeard')
-            sub_logger = logging.getLogger('subliminal')
-            imdb_logger = logging.getLogger('imdbpy')
-            tornado_logger = logging.getLogger('tornado')
-            feedcache_logger = logging.getLogger('feedcache')
-
-            sb_logger.removeHandler(handler)
-            sub_logger.removeHandler(handler)
-            imdb_logger.removeHandler(handler)
-            tornado_logger.removeHandler(handler)
-            feedcache_logger.removeHandler(handler)
-
-            handler.flush()
-            handler.close()
-
-    def initLogging(self, consoleLogging=False):
-
-        if consoleLogging:
-            self.console_logging = consoleLogging
-
-        old_handler = None
-
-        # get old handler in case we want to close it
-        if self.cur_handler:
-            old_handler = self.cur_handler
-        else:
-
-            #Add a new logging level DB
-            logging.addLevelName(5, 'DB')
-
-            # only start consoleLogging on first initialize
-            if self.console_logging:
-                # define a Handler which writes INFO messages or higher to the sys.stderr
-                console = logging.StreamHandler()
-
-                console.setLevel(logging.INFO)
-                if sickbeard.DEBUG:
-                    console.setLevel(logging.DEBUG)
-
-                # set a format which is simpler for console use
-                console.setFormatter(DispatchingFormatter(
-                    {'sickbeard': logging.Formatter('%(asctime)s %(levelname)s::%(message)s', '%H:%M:%S'),
-                     'subliminal': logging.Formatter('%(asctime)s %(levelname)s::SUBLIMINAL :: %(message)s',
-                                                     '%H:%M:%S'),
-                     'imdbpy': logging.Formatter('%(asctime)s %(levelname)s::IMDBPY :: %(message)s', '%H:%M:%S'),
-                     'tornado.general': logging.Formatter('%(asctime)s %(levelname)s::TORNADO :: %(message)s', '%H:%M:%S'),
-                     'tornado.application': logging.Formatter('%(asctime)s %(levelname)s::TORNADO :: %(message)s', '%H:%M:%S'),
-                     'feedcache.cache': logging.Formatter('%(asctime)s %(levelname)s::FEEDCACHE :: %(message)s',
-                                                          '%H:%M:%S')
-                    },
-                    logging.Formatter('%(message)s'), ))
-
-                # add the handler to the root logger
-                logging.getLogger('sickbeard').addHandler(console)
-                logging.getLogger('tornado.general').addHandler(console)
-                logging.getLogger('tornado.application').addHandler(console)
-                logging.getLogger('subliminal').addHandler(console)
-                logging.getLogger('imdbpy').addHandler(console)
-                logging.getLogger('feedcache').addHandler(console)
-
-        self.log_file_path = os.path.join(sickbeard.LOG_DIR, self.log_file)
-
-        self.cur_handler = self._config_handler()
-        logging.getLogger('sickbeard').addHandler(self.cur_handler)
-        logging.getLogger('tornado.access').addHandler(NullHandler())
-        logging.getLogger('tornado.general').addHandler(self.cur_handler)
-        logging.getLogger('tornado.application').addHandler(self.cur_handler)
-        logging.getLogger('subliminal').addHandler(self.cur_handler)
-        logging.getLogger('imdbpy').addHandler(self.cur_handler)
-        logging.getLogger('feedcache').addHandler(self.cur_handler)
-
-        logging.getLogger('sickbeard').setLevel(DB)
-
-        log_level = logging.WARNING
-        if sickbeard.DEBUG:
-            log_level = logging.DEBUG
-
-        logging.getLogger('tornado.general').setLevel(log_level)
-        logging.getLogger('tornado.application').setLevel(log_level)
-        logging.getLogger('subliminal').setLevel(log_level)
-        logging.getLogger('imdbpy').setLevel(log_level)
-        logging.getLogger('feedcache').setLevel(log_level)
-
-
-        # already logging in new log folder, close the old handler
-        if old_handler:
-            self.close_log(old_handler)
-            #            old_handler.flush()
-            #            old_handler.close()
-            #            sb_logger = logging.getLogger('sickbeard')
-            #            sub_logger = logging.getLogger('subliminal')
-            #            imdb_logger = logging.getLogger('imdbpy')
-            #            sb_logger.removeHandler(old_handler)
-            #            subli_logger.removeHandler(old_handler)
-            #            imdb_logger.removeHandler(old_handler)
-
-    def _config_handler(self):
-        """
-        Configure a file handler to log at file_name and return it.
-        """
-
-        file_handler = logging.FileHandler(self.log_file_path, encoding='utf-8')
-        file_handler.setLevel(DB)
-        file_handler.setFormatter(DispatchingFormatter(
-            {'sickbeard': logging.Formatter('%(asctime)s %(levelname)-8s %(message)s', '%Y-%m-%d %H:%M:%S'),
-             'subliminal': logging.Formatter('%(asctime)s %(levelname)-8s SUBLIMINAL :: %(message)s',
-                                             '%Y-%m-%d %H:%M:%S'),
-             'imdbpy': logging.Formatter('%(asctime)s %(levelname)-8s IMDBPY :: %(message)s', '%Y-%m-%d %H:%M:%S'),
-             'tornado.general': logging.Formatter('%(asctime)s %(levelname)-8s TORNADO :: %(message)s', '%Y-%m-%d %H:%M:%S'),
-             'tornado.application': logging.Formatter('%(asctime)s %(levelname)-8s TORNADO :: %(message)s', '%Y-%m-%d %H:%M:%S'),
-             'feedcache.cache': logging.Formatter('%(asctime)s %(levelname)-8s FEEDCACHE :: %(message)s',
-                                                      '%Y-%m-%d %H:%M:%S')
-            },
-            logging.Formatter('%(message)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_path + ('.' + 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')
-        sub_logger = logging.getLogger('subliminal')
-        imdb_logger = logging.getLogger('imdbpy')
-        tornado_logger = logging.getLogger('tornado')
-        feedcache_logger = logging.getLogger('feedcache')
-
-        # delete the old handler
-        if self.cur_handler:
-            self.close_log()
-
-        # 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:
-                    if sickbeard.TRASH_ROTATE_LOGS:
-                        new_name = '%s.%s' % (cur_file_name, int(time.time()))
-                        os.rename(cur_file_name, new_name)
-                        send2trash(new_name)
-                    else:
-                        os.remove(cur_file_name)
-                else:
-                    os.rename(cur_file_name, self._log_file_name(i + 1))
-            except OSError:
-                pass
-
-        # the new log handler will always be on the un-numbered .log file
-        new_file_handler = self._config_handler()
-
-        self.cur_handler = new_file_handler
-
-        sb_logger.addHandler(new_file_handler)
-        sub_logger.addHandler(new_file_handler)
-        imdb_logger.addHandler(new_file_handler)
-        tornado_logger.addHandler(new_file_handler)
-        feedcache_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_path) and os.path.getsize(self.log_file_path) >= LOG_SIZE:
-                    self._rotate_logs()
-                self.writes_since_check = 0
-            else:
-                self.writes_since_check += 1
-
-            meThread = threading.currentThread().getName()
-            message = meThread + u" :: " + toLog
-
-            out_line = message
-
-            sb_logger = logging.getLogger('sickbeard')
-            setattr(sb_logger, 'db', lambda *args: sb_logger.log(DB, *args))
-
-            sub_logger = logging.getLogger('subliminal')
-            imdb_logger = logging.getLogger('imdbpy')
-            tornado_logger = logging.getLogger('tornado')
-            feedcache_logger = logging.getLogger('feedcache')
-
-            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))
-                elif logLevel == DB:
-                    sb_logger.db(out_line)
-                else:
-                    sb_logger.log(logLevel, out_line)
-            except ValueError:
-                pass
-
-    def log_error_and_exit(self, error_msg):
-        log(error_msg, ERROR)
-
-        if not self.console_logging:
+class CensoredFormatter(logging.Formatter):
+    def format(self, record):
+        msg = super(CensoredFormatter, self).format(record)
+        for k, v in censoredItems.items():
+            if v and len(v) > 0 and v in msg:
+                msg = msg.replace(v, len(v) * '*')
+        return msg
+
+class Logger(object):
+    def __init__(self):
+        self.logger = logging.getLogger('sickrage')
+
+        self.loggers = [
+            logging.getLogger('sickrage'),
+            logging.getLogger('tornado.general'),
+            logging.getLogger('tornado.application'),
+            #logging.getLogger('tornado.access'),
+        ]
+
+        self.consoleLogging = False
+        self.fileLogging = False
+        self.debugLogging = False
+        self.logFile = None
+
+    def initLogging(self, consoleLogging=False, fileLogging=False, debugLogging=False):
+        self.logFile = self.logFile or os.path.join(sickbeard.LOG_DIR, 'sickrage.log')
+        self.debugLogging = debugLogging
+        self.consoleLogging = consoleLogging
+        self.fileLogging = fileLogging
+
+        # add a new logging level DB
+        logging.addLevelName(DB, 'DB')
+
+        # nullify root logger
+        logging.getLogger().addHandler(NullHandler())
+
+        # set custom root logger
+        for logger in self.loggers:
+            if logger is not self.logger:
+                logger.root = self.logger
+                logger.parent = self.logger
+
+        # set minimum logging level allowed for loggers
+        for logger in self.loggers:
+            logger.setLevel(DB)
+
+        # console log handler
+        if self.consoleLogging:
+            console = logging.StreamHandler()
+            console.setFormatter(CensoredFormatter('%(asctime)s %(levelname)s::%(message)s', '%H:%M:%S'))
+            console.setLevel(INFO if not self.debugLogging else DEBUG)
+
+            for logger in self.loggers:
+                logger.addHandler(console)
+
+        # rotating log file handler
+        if self.fileLogging:
+            rfh = logging.handlers.RotatingFileHandler(self.logFile, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
+            rfh.setFormatter(CensoredFormatter('%(asctime)s %(levelname)-8s %(message)s', '%Y-%m-%d %H:%M:%S'))
+            rfh.setLevel(DEBUG)
+
+            for logger in self.loggers:
+                logger.addHandler(rfh)
+
+    def log(self, msg, level=INFO, *args, **kwargs):
+        meThread = threading.currentThread().getName()
+        message = meThread + u" :: " + msg
+
+        # pass exception information if debugging enabled
+        if level == ERROR:
+            kwargs["exc_info"] = 1
+            classes.ErrorViewer.add(classes.UIError(message))
+
+        self.logger.log(level, message, *args, **kwargs)
+
+    def log_error_and_exit(self, error_msg, *args, **kwargs):
+        self.log(error_msg, ERROR, *args, **kwargs)
+
+        if not self.consoleLogging:
             sys.exit(error_msg.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace'))
         else:
             sys.exit(1)
 
+class Wrapper(object):
+    instance = Logger()
 
-class DispatchingFormatter:
-    def __init__(self, formatters, default_formatter):
-        self._formatters = formatters
-        self._default_formatter = default_formatter
-
-    def __del__(self):
-        pass
-
-    def format(self, record):
-        formatter = self._formatters.get(record.name, self._default_formatter)
-        return formatter.format(record)
-
-
-sb_log_instance = SBRotatingLogHandler('sickbeard.log', NUM_LOGS, LOG_SIZE)
-
-
-def log(toLog, logLevel=MESSAGE):
-    sb_log_instance.log(toLog, logLevel)
-
+    def __init__(self, wrapped):
+        self.wrapped = wrapped
 
-def log_error_and_exit(error_msg):
-    sb_log_instance.log_error_and_exit(error_msg)
+    def __getattr__(self, name):
+        try:
+            return getattr(self.wrapped, name)
+        except AttributeError:
+            return getattr(self.instance, name)
 
+_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])
 
-def close():
-    sb_log_instance.close_log()    
\ No newline at end of file
diff --git a/sickbeard/metadata/__init__.py b/sickbeard/metadata/__init__.py
index 6a01fdcf33f8e80772733b396e89a305f9cfdd49..3645c5b4ec35a32cf5cb3cd53dcf63aa81dadfa7 100644
--- a/sickbeard/metadata/__init__.py
+++ b/sickbeard/metadata/__init__.py
@@ -16,10 +16,10 @@
 # You should have received a copy of the GNU General Public License
 # along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
 
-__all__ = ['generic', 'helpers', 'xbmc', 'xbmc_12plus', 'mediabrowser', 'ps3', 'wdtv', 'tivo', 'mede8er']
+__all__ = ['generic', 'helpers', 'kodi', 'kodi_12plus', 'mediabrowser', 'ps3', 'wdtv', 'tivo', 'mede8er']
 
 import sys
-import xbmc, xbmc_12plus, mediabrowser, ps3, wdtv, tivo, mede8er
+import kodi, kodi_12plus, mediabrowser, ps3, wdtv, tivo, mede8er
 
 
 def available_generators():
diff --git a/sickbeard/metadata/generic.py b/sickbeard/metadata/generic.py
index 4234404dafffc686a9c3beb97f741d28212eab04..c623be2b5d46b0744c7e1fc6551e8f67e5421abf 100644
--- a/sickbeard/metadata/generic.py
+++ b/sickbeard/metadata/generic.py
@@ -39,7 +39,7 @@ from lib.tmdb_api.tmdb_api import TMDB
 class GenericMetadata():
     """
     Base class for all metadata providers. Default behavior is meant to mostly
-    follow XBMC 12+ metadata standards. Has support for:
+    follow KODI 12+ metadata standards. Has support for:
     - show metadata file
     - episode metadata file
     - episode thumbnail
diff --git a/sickbeard/metadata/xbmc.py b/sickbeard/metadata/kodi.py
similarity index 96%
rename from sickbeard/metadata/xbmc.py
rename to sickbeard/metadata/kodi.py
index 3e26756ff60e508784f75b6d352d1ac02b6cc526..71d1eedf8c41bbfb15ab23fd433429001bf82ae7 100644
--- a/sickbeard/metadata/xbmc.py
+++ b/sickbeard/metadata/kodi.py
@@ -17,7 +17,7 @@
 # along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
 
 import generic
-import xbmc_12plus
+import kodi_12plus
 
 import os
 
@@ -25,9 +25,9 @@ from sickbeard import helpers
 from sickbeard import encodingKludge as ek
 
 
-class XBMCMetadata(xbmc_12plus.XBMC_12PlusMetadata):
+class KODIMetadata(kodi_12plus.KODI_12PlusMetadata):
     """
-    Metadata generation class for XBMC (legacy).
+    Metadata generation class for KODI (legacy).
 
     The following file structure is used:
 
@@ -66,7 +66,7 @@ class XBMCMetadata(xbmc_12plus.XBMC_12PlusMetadata):
                                          season_all_poster,
                                          season_all_banner)
 
-        self.name = 'XBMC'
+        self.name = 'KODI'
 
         self.poster_name = self.banner_name = "folder.jpg"
         self.season_all_poster_name = "season-all.tbn"
@@ -123,4 +123,4 @@ class XBMCMetadata(xbmc_12plus.XBMC_12PlusMetadata):
 
 
 # present a standard "interface" from the module
-metadata_class = XBMCMetadata
+metadata_class = KODIMetadata
diff --git a/sickbeard/metadata/xbmc_12plus.py b/sickbeard/metadata/kodi_12plus.py
similarity index 94%
rename from sickbeard/metadata/xbmc_12plus.py
rename to sickbeard/metadata/kodi_12plus.py
index b1664dd1e8474a46c882d269d694d29e5bfc4519..9a916b6d357a98ca1d09cceb007dd009b1f92663 100644
--- a/sickbeard/metadata/xbmc_12plus.py
+++ b/sickbeard/metadata/kodi_12plus.py
@@ -26,9 +26,9 @@ from sickbeard.exceptions import ex
 import xml.etree.cElementTree as etree
 
 
-class XBMC_12PlusMetadata(generic.GenericMetadata):
+class KODI_12PlusMetadata(generic.GenericMetadata):
     """
-    Metadata generation class for XBMC 12+.
+    Metadata generation class for KODI 12+.
 
     The following file structure is used:
 
@@ -69,7 +69,7 @@ class XBMC_12PlusMetadata(generic.GenericMetadata):
                                          season_all_poster,
                                          season_all_banner)
 
-        self.name = 'XBMC 12+'
+        self.name = 'KODI 12+'
 
         self.poster_name = "poster.jpg"
         self.season_all_poster_name = "season-all-poster.jpg"
@@ -88,7 +88,7 @@ class XBMC_12PlusMetadata(generic.GenericMetadata):
 
     def _show_data(self, show_obj):
         """
-        Creates an elementTree XML structure for an XBMC-style tvshow.nfo and
+        Creates an elementTree XML structure for an KODI-style tvshow.nfo and
         returns the resulting data object.
 
         show_obj: a TVShow instance to create the NFO for
@@ -189,19 +189,16 @@ class XBMC_12PlusMetadata(generic.GenericMetadata):
                 cur_actor = etree.SubElement(tv_node, "actor")
 
                 cur_actor_name = etree.SubElement(cur_actor, "name")
-                cur_actor_name_text = actor['name']
-                if isinstance(cur_actor_name_text, basestring):
-                    cur_actor_name.text = cur_actor_name_text.strip()
+                if getattr(actor, 'name', None) is not None:
+                    cur_actor_name.text = actor['name'].strip()
 
                 cur_actor_role = etree.SubElement(cur_actor, "role")
-                cur_actor_role_text = actor['role']
-                if cur_actor_role_text != None:
-                    cur_actor_role.text = cur_actor_role_text
+                if getattr(actor, 'role', None) is not None:
+                    cur_actor_role.text = actor['role']
 
                 cur_actor_thumb = etree.SubElement(cur_actor, "thumb")
-                cur_actor_thumb_text = actor['image']
-                if cur_actor_thumb_text != None:
-                    cur_actor_thumb.text = cur_actor_thumb_text
+                if getattr(actor, 'image', None) is not None:
+                    cur_actor_thumb.text = actor['image']
 
         # Make it purdy
         helpers.indentXML(tv_node)
@@ -212,7 +209,7 @@ class XBMC_12PlusMetadata(generic.GenericMetadata):
 
     def _ep_data(self, ep_obj):
         """
-        Creates an elementTree XML structure for an XBMC-style episode.nfo and
+        Creates an elementTree XML structure for an KODI-style episode.nfo and
         returns the resulting data object.
             show_obj: a TVEpisode instance to create the NFO for
         """
@@ -242,7 +239,7 @@ class XBMC_12PlusMetadata(generic.GenericMetadata):
             return
 
         if len(eps_to_write) > 1:
-            rootNode = etree.Element("xbmcmultiepisode")
+            rootNode = etree.Element("kodimultiepisode")
         else:
             rootNode = etree.Element("episodedetails")
 
@@ -373,4 +370,4 @@ class XBMC_12PlusMetadata(generic.GenericMetadata):
 
 
 # present a standard "interface" from the module
-metadata_class = XBMC_12PlusMetadata
+metadata_class = KODI_12PlusMetadata
diff --git a/sickbeard/metadata/mede8er.py b/sickbeard/metadata/mede8er.py
index e90bca7eaeb3d354c3140815eb7d6ccf2b67d37c..9e3d836eacaf3a009f9e0b61b9395ec2a60665a2 100644
--- a/sickbeard/metadata/mede8er.py
+++ b/sickbeard/metadata/mede8er.py
@@ -199,8 +199,7 @@ class Mede8erMetadata(mediabrowser.MediaBrowserMetadata):
 
         if getattr(myShow, '_actors', None) is not None:
             for actor in myShow['_actors']:
-                cur_actor_name_text = actor['name']
-
+                cur_actor_name_text = getattr(actor, 'name', None)
                 if cur_actor_name_text != None and cur_actor_name_text.strip():
                     cur_actor = etree.SubElement(cast, "actor")
                     cur_actor.text = cur_actor_name_text.strip()
diff --git a/sickbeard/metadata/mediabrowser.py b/sickbeard/metadata/mediabrowser.py
index 5f303fed1bb9f052ca75556fdc224dba146afc65..b35c076fcb9a026f2a5d8b01addb53ae528f143c 100644
--- a/sickbeard/metadata/mediabrowser.py
+++ b/sickbeard/metadata/mediabrowser.py
@@ -154,7 +154,7 @@ class MediaBrowserMetadata(generic.GenericMetadata):
         for cur_dir in dir_list:
             # MediaBrowser 1.x only supports 'Specials'
             # MediaBrowser 2.x looks to only support 'Season 0'
-            # MediaBrowser 3.x looks to mimic XBMC/Plex support
+            # MediaBrowser 3.x looks to mimic KODI/Plex support
             if season == 0 and cur_dir == "Specials":
                 season_dir = cur_dir
                 break
@@ -194,7 +194,7 @@ class MediaBrowserMetadata(generic.GenericMetadata):
         for cur_dir in dir_list:
             # MediaBrowser 1.x only supports 'Specials'
             # MediaBrowser 2.x looks to only support 'Season 0'
-            # MediaBrowser 3.x looks to mimic XBMC/Plex support
+            # MediaBrowser 3.x looks to mimic KODI/Plex support
             if season == 0 and cur_dir == "Specials":
                 season_dir = cur_dir
                 break
@@ -362,14 +362,17 @@ class MediaBrowserMetadata(generic.GenericMetadata):
         if getattr(myShow, 'actors', None) is not None:
             for actor in myShow['_actors']:
                 cur_actor = etree.SubElement(Persons, "Person")
+
                 cur_actor_name = etree.SubElement(cur_actor, "Name")
-                cur_actor_name.text = actor['name'].strip()
+                if getattr(actor, 'name', None):
+                    cur_actor_name.text = actor['name'].strip()
+
                 cur_actor_type = etree.SubElement(cur_actor, "Type")
                 cur_actor_type.text = "Actor"
+
                 cur_actor_role = etree.SubElement(cur_actor, "Role")
-                cur_actor_role_text = actor['role']
-                if cur_actor_role_text != None:
-                    cur_actor_role.text = cur_actor_role_text
+                if getattr(actor, 'role', None):
+                    cur_actor_role.text = actor['role']
 
         helpers.indentXML(tv_node)
 
diff --git a/sickbeard/metadata/wdtv.py b/sickbeard/metadata/wdtv.py
index f6025796b87ca0c2ca72616c2e2d199069638759..fe4ed63b606d80474231125b2542d76b4a4ab988 100644
--- a/sickbeard/metadata/wdtv.py
+++ b/sickbeard/metadata/wdtv.py
@@ -275,12 +275,14 @@ class WDTVMetadata(generic.GenericMetadata):
             if getattr(myShow, '_actors', None) is not None:
                 for actor in myShow['_actors']:
                     cur_actor = etree.SubElement(episode, "actor")
+
                     cur_actor_name = etree.SubElement(cur_actor, "name")
-                    cur_actor_name.text = actor['name']
+                    if getattr(actor, 'name', None):
+                        cur_actor_name.text = actor['name']
+
                     cur_actor_role = etree.SubElement(cur_actor, "role")
-                    cur_actor_role_text = actor['role']
-                    if cur_actor_role_text != None:
-                        cur_actor_role.text = cur_actor_role_text
+                    if getattr(actor, 'role', None):
+                        cur_actor_role.text = actor['role']
 
             overview = etree.SubElement(episode, "overview")
             if curEpToWrite.description != None:
diff --git a/sickbeard/name_cache.py b/sickbeard/name_cache.py
index 15e208f0ab81d1355ebdf3e1e861254d567a9298..17c49eb97536f9fad9c7ec7308ce01073a552337 100644
--- a/sickbeard/name_cache.py
+++ b/sickbeard/name_cache.py
@@ -91,7 +91,7 @@ def buildNameCache(show=None):
         sickbeard.scene_exceptions.retrieve_exceptions()
 
         if not show:
-            logger.log(u"Building internal name cache for all shows", logger.MESSAGE)
+            logger.log(u"Building internal name cache for all shows", logger.INFO)
 
             cacheDB = db.DBConnection('cache.db')
             cache_results = cacheDB.select("SELECT * FROM scene_names")
@@ -114,7 +114,7 @@ def buildNameCache(show=None):
 
                         nameCache[name] = int(show.indexerid)
         else:
-            logger.log(u"Building internal name cache for " + show.name, logger.MESSAGE)
+            logger.log(u"Building internal name cache for " + show.name, logger.INFO)
 
             for curSeason in [-1] + sickbeard.scene_exceptions.get_scene_seasons(show.indexerid):
                 for name in list(set(sickbeard.scene_exceptions.get_scene_exceptions(show.indexerid, season=curSeason) + [
diff --git a/sickbeard/name_parser/regexes.py b/sickbeard/name_parser/regexes.py
index e36988cd860d41fb155b176fead0df4c0da56f41..ec9543a32a6525015d1bb8e281dff10941447718 100644
--- a/sickbeard/name_parser/regexes.py
+++ b/sickbeard/name_parser/regexes.py
@@ -56,10 +56,10 @@ normal_regexes = [
      # Show.Name.S01.E02.E03
      '''
      ^((?P<series_name>.+?)[. _-]+)?             # Show_Name and separator
-     s(?P<season_num>\d+)[. _-]*                 # S01 and optional separator
-     e(?P<ep_num>\d+)                            # E02 and separator
+     (\()?s(?P<season_num>\d+)[. _-]*            # S01 and optional separator
+     e(?P<ep_num>\d+)(\))?                       # E02 and separator
      (([. _-]*e|-)                               # linking e/- char
-     (?P<extra_ep_num>(?!(1080|720|480)[pi])\d+))*   # additional E03/etc
+     (?P<extra_ep_num>(?!(1080|720|480)[pi])\d+)(\))?)*   # additional E03/etc
      [. _-]*((?P<extra_info>.+?)                 # Source_Quality_Etc-
      ((?<![. _-])(?<!WEB)                        # Make sure this is really the release group
      -(?P<release_group>[^- ]+([. _-]\[.*\])?))?)?$              # Group
diff --git a/sickbeard/network_timezones.py b/sickbeard/network_timezones.py
index cdf0f2925c87d2d00fbc7c058ebfca8b98392640..979a71e2d8ee5be1b4b07f8b5da01ba0dc210581 100644
--- a/sickbeard/network_timezones.py
+++ b/sickbeard/network_timezones.py
@@ -75,19 +75,23 @@ def _update_zoneinfo():
     # now check if the zoneinfo needs update
     url_zv = 'https://raw.githubusercontent.com/Prinz23/sb_network_timezones/master/zoneinfo.txt'
 
-    url_data = helpers.getURL(url_zv)
-    if url_data is None:
+    try:
+        url_data = helpers.getURL(url_zv)
+        if not url_data:
+            raise
+
+        if lib.dateutil.zoneinfo.ZONEINFOFILE is not None:
+            cur_zoneinfo = ek.ek(basename, lib.dateutil.zoneinfo.ZONEINFOFILE)
+        else:
+            cur_zoneinfo = None
+
+        (new_zoneinfo, zoneinfo_md5) = url_data.decode('utf-8').strip().rsplit(u' ')
+    except:
         # When urlData is None, trouble connecting to github
         logger.log(u'Loading zoneinfo.txt failed, this can happen from time to time. Unable to get URL: %s' % url_zv,
                    logger.WARNING)
         return
 
-    if lib.dateutil.zoneinfo.ZONEINFOFILE is not None:
-        cur_zoneinfo = ek.ek(basename, lib.dateutil.zoneinfo.ZONEINFOFILE)
-    else:
-        cur_zoneinfo = None
-    (new_zoneinfo, zoneinfo_md5) = url_data.decode('utf-8').strip().rsplit(u' ')
-
     if (cur_zoneinfo is not None) and (new_zoneinfo == cur_zoneinfo):
         return
 
@@ -114,7 +118,7 @@ def _update_zoneinfo():
     new_hash = str(helpers.md5_for_file(zonefile_tmp))
 
     if zoneinfo_md5.upper() == new_hash.upper():
-        logger.log(u'Updating timezone info with new one: %s' % new_zoneinfo, logger.MESSAGE)
+        logger.log(u'Updating timezone info with new one: %s' % new_zoneinfo, logger.INFO)
         try:
             # remove the old zoneinfo file
             if cur_zoneinfo is not None:
diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py
index b8299fb4d722ec6f4d2b32e6a1420754a62bde57..ad718dc800cf3d6de7bd3d77747eaff7fdaa515a 100644
--- a/sickbeard/notifiers/__init__.py
+++ b/sickbeard/notifiers/__init__.py
@@ -18,7 +18,7 @@
 
 import sickbeard
 
-import xbmc
+import kodi
 import plex
 import nmj
 import nmjv2
@@ -43,7 +43,7 @@ import emailnotify
 from sickbeard.common import *
 
 # home theater / nas
-xbmc_notifier = xbmc.XBMCNotifier()
+kodi_notifier = kodi.KODINotifier()
 plex_notifier = plex.PLEXNotifier()
 nmj_notifier = nmj.NMJNotifier()
 nmjv2_notifier = nmjv2.NMJv2Notifier()
@@ -67,7 +67,7 @@ email_notifier = emailnotify.EmailNotifier()
 
 notifiers = [
     libnotify_notifier,  # Libnotify notifier goes first because it doesn't involve blocking on network activity.
-    xbmc_notifier,
+    kodi_notifier,
     plex_notifier,
     nmj_notifier,
     nmjv2_notifier,
diff --git a/sickbeard/notifiers/boxcar.py b/sickbeard/notifiers/boxcar.py
index 3ac83a2e23936ca83fea5eb0b875e6123e70acbf..a60f7f624d1afb030fd23f16a80ad19dab48c0ed 100644
--- a/sickbeard/notifiers/boxcar.py
+++ b/sickbeard/notifiers/boxcar.py
@@ -107,7 +107,7 @@ class BoxcarNotifier:
                 logger.log("Wrong data sent to boxcar", logger.ERROR)
                 return False
 
-        logger.log("Boxcar notification successful.", logger.MESSAGE)
+        logger.log("Boxcar notification successful.", logger.INFO)
         return True
 
     def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]):
diff --git a/sickbeard/notifiers/emailnotify.py b/sickbeard/notifiers/emailnotify.py
index 1dac6758a488e7ced184a75175b574c0fe1f01c1..74d292beb0285edabfeba436428ebf20c9e7ac32 100644
--- a/sickbeard/notifiers/emailnotify.py
+++ b/sickbeard/notifiers/emailnotify.py
@@ -29,7 +29,7 @@ import sickbeard
 
 from sickbeard import logger, common
 from sickbeard import db
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
 from sickbeard.exceptions import ex
 
 
@@ -51,7 +51,7 @@ class EmailNotifier:
         ep_name: The name of the episode that was snatched
         title: The title of the notification (optional)
         """
-        ep_name = toUnicode(ep_name)
+        ep_name = ek.ss(ep_name)
 
         if sickbeard.EMAIL_NOTIFY_ONSNATCH:
             show = self._parseEp(ep_name)
@@ -86,7 +86,7 @@ class EmailNotifier:
         ep_name: The name of the episode that was downloaded
         title: The title of the notification (optional)
         """
-        ep_name = toUnicode(ep_name)
+        ep_name = ek.ss(ep_name)
 
         if sickbeard.EMAIL_NOTIFY_ONDOWNLOAD:
             show = self._parseEp(ep_name)
@@ -121,7 +121,7 @@ class EmailNotifier:
         ep_name: The name of the episode that was downloaded
         lang: Subtitle language wanted
         """
-        ep_name = toUnicode(ep_name)
+        ep_name = ek.ss(ep_name)
 
         if sickbeard.EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD:
             show = self._parseEp(ep_name)
@@ -198,7 +198,7 @@ class EmailNotifier:
             return False
 
     def _parseEp(self, ep_name):
-        ep_name = toUnicode(ep_name)
+        ep_name = ek.ss(ep_name)
 
         sep = " - "
         titles = ep_name.split(sep)
diff --git a/sickbeard/notifiers/xbmc.py b/sickbeard/notifiers/kodi.py
similarity index 70%
rename from sickbeard/notifiers/xbmc.py
rename to sickbeard/notifiers/kodi.py
index 49683bba5d75ea1083892efdbedbae318e22e4ba..c297bfc685e6f47c17a29433eb2d252c7dc353d4 100644
--- a/sickbeard/notifiers/xbmc.py
+++ b/sickbeard/notifiers/kodi.py
@@ -26,7 +26,7 @@ import sickbeard
 from sickbeard import logger
 from sickbeard import common
 from sickbeard.exceptions import ex
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
 
 
 try:
@@ -40,27 +40,27 @@ except ImportError:
     from lib import simplejson as json
 
 
-class XBMCNotifier:
+class KODINotifier:
     sb_logo_url = 'https://raw.githubusercontent.com/SiCKRAGETV/SickRage/master/gui/slick/images/sickrage-shark-mascot.png'
 
-    def _get_xbmc_version(self, host, username, password):
-        """Returns XBMC JSON-RPC API version (odd # = dev, even # = stable)
+    def _get_kodi_version(self, host, username, password):
+        """Returns KODI JSON-RPC API version (odd # = dev, even # = stable)
 
-        Sends a request to the XBMC host using the JSON-RPC to determine if
+        Sends a request to the KODI host using the JSON-RPC to determine if
         the legacy API or if the JSON-RPC API functions should be used.
 
         Fallback to testing legacy HTTPAPI before assuming it is just a badly configured host.
 
         Args:
-            host: XBMC webserver host:port
-            username: XBMC webserver username
-            password: XBMC webserver password
+            host: KODI webserver host:port
+            username: KODI webserver username
+            password: KODI webserver password
 
         Returns:
             Returns API number or False
 
             List of possible known values:
-                API | XBMC Version
+                API | KODI Version
                -----+---------------
                  2  | v10 (Dharma)
                  3  | (pre Eden)
@@ -75,7 +75,7 @@ class XBMCNotifier:
         socket.setdefaulttimeout(10)
 
         checkCommand = '{"jsonrpc":"2.0","method":"JSONRPC.Version","id":1}'
-        result = self._send_to_xbmc_json(checkCommand, host, username, password)
+        result = self._send_to_kodi_json(checkCommand, host, username, password)
 
         # revert back to default socket timeout
         socket.setdefaulttimeout(sickbeard.SOCKET_TIMEOUT)
@@ -85,14 +85,14 @@ class XBMCNotifier:
         else:
             # fallback to legacy HTTPAPI method
             testCommand = {'command': 'Help'}
-            request = self._send_to_xbmc(testCommand, host, username, password)
+            request = self._send_to_kodi(testCommand, host, username, password)
             if request:
                 # return a fake version number, so it uses the legacy method
                 return 1
             else:
                 return False
 
-    def _notify_xbmc(self, message, title="SickRage", host=None, username=None, password=None, force=False):
+    def _notify_kodi(self, message, title="SickRage", host=None, username=None, password=None, force=False):
         """Internal wrapper for the notify_snatch and notify_download functions
 
         Detects JSON-RPC version then branches the logic for either the JSON-RPC or legacy HTTP API methods.
@@ -100,9 +100,9 @@ class XBMCNotifier:
         Args:
             message: Message body of the notice to send
             title: Title of the notice to send
-            host: XBMC webserver host:port
-            username: XBMC webserver username
-            password: XBMC webserver password
+            host: KODI webserver host:port
+            username: KODI webserver username
+            password: KODI webserver password
             force: Used for the Test method to override config saftey checks
 
         Returns:
@@ -113,42 +113,42 @@ class XBMCNotifier:
 
         # fill in omitted parameters
         if not host:
-            host = sickbeard.XBMC_HOST
+            host = sickbeard.KODI_HOST
         if not username:
-            username = sickbeard.XBMC_USERNAME
+            username = sickbeard.KODI_USERNAME
         if not password:
-            password = sickbeard.XBMC_PASSWORD
+            password = sickbeard.KODI_PASSWORD
 
         # suppress notifications if the notifier is disabled but the notify options are checked
-        if not sickbeard.USE_XBMC and not force:
-            logger.log("Notification for XBMC not enabled, skipping this notification", logger.DEBUG)
+        if not sickbeard.USE_KODI and not force:
+            logger.log("Notification for KODI not enabled, skipping this notification", logger.DEBUG)
             return False
 
         result = ''
         for curHost in [x.strip() for x in host.split(",")]:
-            logger.log(u"Sending XBMC notification to '" + curHost + "' - " + message, logger.MESSAGE)
+            logger.log(u"Sending KODI notification to '" + curHost + "' - " + message, logger.INFO)
 
-            xbmcapi = self._get_xbmc_version(curHost, username, password)
-            if xbmcapi:
-                if (xbmcapi <= 4):
-                    logger.log(u"Detected XBMC version <= 11, using XBMC HTTP API", logger.DEBUG)
+            kodiapi = self._get_kodi_version(curHost, username, password)
+            if kodiapi:
+                if (kodiapi <= 4):
+                    logger.log(u"Detected KODI version <= 11, using KODI HTTP API", logger.DEBUG)
                     command = {'command': 'ExecBuiltIn',
                                'parameter': 'Notification(' + title.encode("utf-8") + ',' + message.encode(
                                    "utf-8") + ')'}
-                    notifyResult = self._send_to_xbmc(command, curHost, username, password)
+                    notifyResult = self._send_to_kodi(command, curHost, username, password)
                     if notifyResult:
                         result += curHost + ':' + str(notifyResult)
                 else:
-                    logger.log(u"Detected XBMC version >= 12, using XBMC JSON API", logger.DEBUG)
+                    logger.log(u"Detected KODI version >= 12, using KODI JSON API", logger.DEBUG)
                     command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"%s","message":"%s", "image": "%s"},"id":1}' % (
                         title.encode("utf-8"), message.encode("utf-8"), self.sb_logo_url)
-                    notifyResult = self._send_to_xbmc_json(command, curHost, username, password)
+                    notifyResult = self._send_to_kodi_json(command, curHost, username, password)
                     if notifyResult.get('result'):
                         result += curHost + ':' + notifyResult["result"].decode(sickbeard.SYS_ENCODING)
             else:
-                if sickbeard.XBMC_ALWAYS_ON or force:
+                if sickbeard.KODI_ALWAYS_ON or force:
                     logger.log(
-                        u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.",
+                        u"Failed to detect KODI version for '" + curHost + "', check configuration and try again.",
                         logger.ERROR)
                 result += curHost + ':False'
 
@@ -157,10 +157,10 @@ class XBMCNotifier:
     def _send_update_library(self, host, showName=None):
         """Internal wrapper for the update library function to branch the logic for JSON-RPC or legacy HTTP API
 
-        Checks the XBMC API version to branch the logic to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods.
+        Checks the KODI API version to branch the logic to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods.
 
         Args:
-            host: XBMC webserver host:port
+            host: KODI webserver host:port
             showName: Name of a TV show to specifically target the library update for
 
         Returns:
@@ -168,43 +168,43 @@ class XBMCNotifier:
 
         """
 
-        logger.log(u"Sending request to update library for XBMC host: '" + host + "'", logger.MESSAGE)
+        logger.log(u"Sending request to update library for KODI host: '" + host + "'", logger.INFO)
 
-        xbmcapi = self._get_xbmc_version(host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD)
-        if xbmcapi:
-            if (xbmcapi <= 4):
+        kodiapi = self._get_kodi_version(host, sickbeard.KODI_USERNAME, sickbeard.KODI_PASSWORD)
+        if kodiapi:
+            if (kodiapi <= 4):
                 # try to update for just the show, if it fails, do full update if enabled
-                if not self._update_library(host, showName) and sickbeard.XBMC_UPDATE_FULL:
+                if not self._update_library(host, showName) and sickbeard.KODI_UPDATE_FULL:
                     logger.log(u"Single show update failed, falling back to full update", logger.WARNING)
                     return self._update_library(host)
                 else:
                     return True
             else:
                 # try to update for just the show, if it fails, do full update if enabled
-                if not self._update_library_json(host, showName) and sickbeard.XBMC_UPDATE_FULL:
+                if not self._update_library_json(host, showName) and sickbeard.KODI_UPDATE_FULL:
                     logger.log(u"Single show update failed, falling back to full update", logger.WARNING)
                     return self._update_library_json(host)
                 else:
                     return True
         else:
-            logger.log(u"Failed to detect XBMC version for '" + host + "', check configuration and try again.",
+            logger.log(u"Failed to detect KODI version for '" + host + "', check configuration and try again.",
                        logger.DEBUG)
             return False
 
         return False
 
     # #############################################################################
-    # Legacy HTTP API (pre XBMC 12) methods
+    # Legacy HTTP API (pre KODI 12) methods
     ##############################################################################
 
-    def _send_to_xbmc(self, command, host=None, username=None, password=None):
-        """Handles communication to XBMC servers via HTTP API
+    def _send_to_kodi(self, command, host=None, username=None, password=None):
+        """Handles communication to KODI servers via HTTP API
 
         Args:
-            command: Dictionary of field/data pairs, encoded via urllib and passed to the XBMC API via HTTP
-            host: XBMC webserver host:port
-            username: XBMC webserver username
-            password: XBMC webserver password
+            command: Dictionary of field/data pairs, encoded via urllib and passed to the KODI API via HTTP
+            host: KODI webserver host:port
+            username: KODI webserver username
+            password: KODI webserver password
 
         Returns:
             Returns response.result for successful commands or False if there was an error
@@ -213,12 +213,12 @@ class XBMCNotifier:
 
         # fill in omitted parameters
         if not username:
-            username = sickbeard.XBMC_USERNAME
+            username = sickbeard.KODI_USERNAME
         if not password:
-            password = sickbeard.XBMC_PASSWORD
+            password = sickbeard.KODI_PASSWORD
 
         if not host:
-            logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
+            logger.log(u'No KODI host passed, aborting update', logger.DEBUG)
             return False
 
         for key in command:
@@ -226,9 +226,9 @@ class XBMCNotifier:
                 command[key] = command[key].encode('utf-8')
 
         enc_command = urllib.urlencode(command)
-        logger.log(u"XBMC encoded API command: " + enc_command, logger.DEBUG)
+        logger.log(u"KODI encoded API command: " + enc_command, logger.DEBUG)
 
-        url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command)
+        url = 'http://%s/kodiCmds/kodiHttp/?%s' % (host, enc_command)
         try:
             req = urllib2.Request(url)
             # if we have a password, use authentication
@@ -236,30 +236,30 @@ class XBMCNotifier:
                 base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
                 authheader = "Basic %s" % base64string
                 req.add_header("Authorization", authheader)
-                logger.log(u"Contacting XBMC (with auth header) via url: " + toUnicode(url), logger.DEBUG)
+                logger.log(u"Contacting KODI (with auth header) via url: " + ek.ss(url), logger.DEBUG)
             else:
-                logger.log(u"Contacting XBMC via url: " + toUnicode(url), logger.DEBUG)
+                logger.log(u"Contacting KODI via url: " + ek.ss(url), logger.DEBUG)
 
             response = urllib2.urlopen(req)
             result = response.read().decode(sickbeard.SYS_ENCODING)
             response.close()
 
-            logger.log(u"XBMC HTTP response: " + result.replace('\n', ''), logger.DEBUG)
+            logger.log(u"KODI HTTP response: " + result.replace('\n', ''), logger.DEBUG)
             return result
 
         except (urllib2.URLError, IOError), e:
-            logger.log(u"Warning: Couldn't contact XBMC HTTP at " + toUnicode(url) + " " + ex(e),
+            logger.log(u"Warning: Couldn't contact KODI HTTP at " + ek.ss(url) + " " + ex(e),
                        logger.WARNING)
             return False
 
     def _update_library(self, host=None, showName=None):
-        """Handles updating XBMC host via HTTP API
+        """Handles updating KODI host via HTTP API
 
-        Attempts to update the XBMC video library for a specific tv show if passed,
+        Attempts to update the KODI video library for a specific tv show if passed,
         otherwise update the whole library if enabled.
 
         Args:
-            host: XBMC webserver host:port
+            host: KODI webserver host:port
             showName: Name of a TV show to specifically target the library update for
 
         Returns:
@@ -268,14 +268,14 @@ class XBMCNotifier:
         """
 
         if not host:
-            logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
+            logger.log(u'No KODI host passed, aborting update', logger.DEBUG)
             return False
 
         logger.log(u"Updating XMBC library via HTTP method for host: " + host, logger.DEBUG)
 
         # if we're doing per-show
         if showName:
-            logger.log(u"Updating library in XBMC via HTTP method for show " + showName, logger.DEBUG)
+            logger.log(u"Updating library in KODI via HTTP method for show " + showName, logger.DEBUG)
 
             pathSql = 'select path.strPath from path, tvshow, tvshowlinkpath where ' \
                       'tvshow.c00 = "%s" and tvshowlinkpath.idShow = tvshow.idShow ' \
@@ -290,12 +290,12 @@ class XBMCNotifier:
             resetCommand = {'command': 'SetResponseFormat()'}
 
             # set xml response format, if this fails then don't bother with the rest
-            request = self._send_to_xbmc(xmlCommand, host)
+            request = self._send_to_kodi(xmlCommand, host)
             if not request:
                 return False
 
-            sqlXML = self._send_to_xbmc(sqlCommand, host)
-            request = self._send_to_xbmc(resetCommand, host)
+            sqlXML = self._send_to_kodi(sqlCommand, host)
+            request = self._send_to_kodi(resetCommand, host)
 
             if not sqlXML:
                 logger.log(u"Invalid response for " + showName + " on " + host, logger.DEBUG)
@@ -305,7 +305,7 @@ class XBMCNotifier:
             try:
                 et = etree.fromstring(encSqlXML)
             except SyntaxError, e:
-                logger.log(u"Unable to parse XML returned from XBMC: " + ex(e), logger.ERROR)
+                logger.log(u"Unable to parse XML returned from KODI: " + ex(e), logger.ERROR)
                 return False
 
             paths = et.findall('.//field')
@@ -317,40 +317,40 @@ class XBMCNotifier:
             for path in paths:
                 # we do not need it double-encoded, gawd this is dumb
                 unEncPath = urllib.unquote(path.text).decode(sickbeard.SYS_ENCODING)
-                logger.log(u"XBMC Updating " + showName + " on " + host + " at " + unEncPath, logger.DEBUG)
-                updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video, %s)' % (unEncPath)}
-                request = self._send_to_xbmc(updateCommand, host)
+                logger.log(u"KODI Updating " + showName + " on " + host + " at " + unEncPath, logger.DEBUG)
+                updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'KODI.updatelibrary(video, %s)' % (unEncPath)}
+                request = self._send_to_kodi(updateCommand, host)
                 if not request:
                     logger.log(u"Update of show directory failed on " + showName + " on " + host + " at " + unEncPath,
                                logger.ERROR)
                     return False
-                # sleep for a few seconds just to be sure xbmc has a chance to finish each directory
+                # sleep for a few seconds just to be sure kodi has a chance to finish each directory
                 if len(paths) > 1:
                     time.sleep(5)
         # do a full update if requested
         else:
-            logger.log(u"Doing Full Library XBMC update on host: " + host, logger.MESSAGE)
-            updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}
-            request = self._send_to_xbmc(updateCommand, host)
+            logger.log(u"Doing Full Library KODI update on host: " + host, logger.INFO)
+            updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'KODI.updatelibrary(video)'}
+            request = self._send_to_kodi(updateCommand, host)
 
             if not request:
-                logger.log(u"XBMC Full Library update failed on: " + host, logger.ERROR)
+                logger.log(u"KODI Full Library update failed on: " + host, logger.ERROR)
                 return False
 
         return True
 
     ##############################################################################
-    # JSON-RPC API (XBMC 12+) methods
+    # JSON-RPC API (KODI 12+) methods
     ##############################################################################
 
-    def _send_to_xbmc_json(self, command, host=None, username=None, password=None):
-        """Handles communication to XBMC servers via JSONRPC
+    def _send_to_kodi_json(self, command, host=None, username=None, password=None):
+        """Handles communication to KODI servers via JSONRPC
 
         Args:
-            command: Dictionary of field/data pairs, encoded via urllib and passed to the XBMC JSON-RPC via HTTP
-            host: XBMC webserver host:port
-            username: XBMC webserver username
-            password: XBMC webserver password
+            command: Dictionary of field/data pairs, encoded via urllib and passed to the KODI JSON-RPC via HTTP
+            host: KODI webserver host:port
+            username: KODI webserver username
+            password: KODI webserver password
 
         Returns:
             Returns response.result for successful commands or False if there was an error
@@ -359,16 +359,16 @@ class XBMCNotifier:
 
         # fill in omitted parameters
         if not username:
-            username = sickbeard.XBMC_USERNAME
+            username = sickbeard.KODI_USERNAME
         if not password:
-            password = sickbeard.XBMC_PASSWORD
+            password = sickbeard.KODI_PASSWORD
 
         if not host:
-            logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
+            logger.log(u'No KODI host passed, aborting update', logger.DEBUG)
             return False
 
         command = command.encode('utf-8')
-        logger.log(u"XBMC JSON command: " + command, logger.DEBUG)
+        logger.log(u"KODI JSON command: " + command, logger.DEBUG)
 
         url = 'http://%s/jsonrpc' % (host)
         try:
@@ -379,14 +379,14 @@ class XBMCNotifier:
                 base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
                 authheader = "Basic %s" % base64string
                 req.add_header("Authorization", authheader)
-                logger.log(u"Contacting XBMC (with auth header) via url: " + toUnicode(url), logger.DEBUG)
+                logger.log(u"Contacting KODI (with auth header) via url: " + ek.ss(url), logger.DEBUG)
             else:
-                logger.log(u"Contacting XBMC via url: " + toUnicode(url), logger.DEBUG)
+                logger.log(u"Contacting KODI via url: " + ek.ss(url), logger.DEBUG)
 
             try:
                 response = urllib2.urlopen(req)
             except urllib2.URLError, e:
-                logger.log(u"Error while trying to retrieve XBMC API version for " + host + ": " + ex(e),
+                logger.log(u"Error while trying to retrieve KODI API version for " + host + ": " + ex(e),
                            logger.WARNING)
                 return False
 
@@ -394,25 +394,25 @@ class XBMCNotifier:
             try:
                 result = json.load(response)
                 response.close()
-                logger.log(u"XBMC JSON response: " + str(result), logger.DEBUG)
+                logger.log(u"KODI JSON response: " + str(result), logger.DEBUG)
                 return result  # need to return response for parsing
             except ValueError, e:
                 logger.log(u"Unable to decode JSON: " + response, logger.WARNING)
                 return False
 
         except IOError, e:
-            logger.log(u"Warning: Couldn't contact XBMC JSON API at " + toUnicode(url) + " " + ex(e),
+            logger.log(u"Warning: Couldn't contact KODI JSON API at " + ek.ss(url) + " " + ex(e),
                        logger.WARNING)
             return False
 
     def _update_library_json(self, host=None, showName=None):
-        """Handles updating XBMC host via HTTP JSON-RPC
+        """Handles updating KODI host via HTTP JSON-RPC
 
-        Attempts to update the XBMC video library for a specific tv show if passed,
+        Attempts to update the KODI video library for a specific tv show if passed,
         otherwise update the whole library if enabled.
 
         Args:
-            host: XBMC webserver host:port
+            host: KODI webserver host:port
             showName: Name of a TV show to specifically target the library update for
 
         Returns:
@@ -421,24 +421,24 @@ class XBMCNotifier:
         """
 
         if not host:
-            logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
+            logger.log(u'No KODI host passed, aborting update', logger.DEBUG)
             return False
 
-        logger.log(u"Updating XMBC library via JSON method for host: " + host, logger.MESSAGE)
+        logger.log(u"Updating XMBC library via JSON method for host: " + host, logger.INFO)
 
         # if we're doing per-show
         if showName:
             tvshowid = -1
-            logger.log(u"Updating library in XBMC via JSON method for show " + showName, logger.DEBUG)
+            logger.log(u"Updating library in KODI via JSON method for show " + showName, logger.DEBUG)
 
             # get tvshowid by showName
             showsCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShows","id":1}'
-            showsResponse = self._send_to_xbmc_json(showsCommand, host)
+            showsResponse = self._send_to_kodi_json(showsCommand, host)
 
             if showsResponse and "result" in showsResponse and "tvshows" in showsResponse["result"]:
                 shows = showsResponse["result"]["tvshows"]
             else:
-                logger.log(u"XBMC: No tvshows in XBMC TV show list", logger.DEBUG)
+                logger.log(u"KODI: No tvshows in KODI TV show list", logger.DEBUG)
                 return False
 
             for show in shows:
@@ -451,13 +451,13 @@ class XBMCNotifier:
 
             # we didn't find the show (exact match), thus revert to just doing a full update if enabled
             if (tvshowid == -1):
-                logger.log(u'Exact show name not matched in XBMC TV show list', logger.DEBUG)
+                logger.log(u'Exact show name not matched in KODI TV show list', logger.DEBUG)
                 return False
 
             # lookup tv-show path
             pathCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShowDetails","params":{"tvshowid":%d, "properties": ["file"]},"id":1}' % (
                 tvshowid)
-            pathResponse = self._send_to_xbmc_json(pathCommand, host)
+            pathResponse = self._send_to_kodi_json(pathCommand, host)
 
             path = pathResponse["result"]["tvshowdetails"]["file"]
             logger.log(u"Received Show: " + showName + " with ID: " + str(tvshowid) + " Path: " + path,
@@ -468,10 +468,10 @@ class XBMCNotifier:
                            logger.WARNING)
                 return False
 
-            logger.log(u"XBMC Updating " + showName + " on " + host + " at " + path, logger.DEBUG)
+            logger.log(u"KODI Updating " + showName + " on " + host + " at " + path, logger.DEBUG)
             updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (
                 json.dumps(path))
-            request = self._send_to_xbmc_json(updateCommand, host)
+            request = self._send_to_kodi_json(updateCommand, host)
             if not request:
                 logger.log(u"Update of show directory failed on " + showName + " on " + host + " at " + path,
                            logger.ERROR)
@@ -487,12 +487,12 @@ class XBMCNotifier:
 
         # do a full update if requested
         else:
-            logger.log(u"Doing Full Library XBMC update on host: " + host, logger.MESSAGE)
+            logger.log(u"Doing Full Library KODI update on host: " + host, logger.INFO)
             updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","id":1}'
-            request = self._send_to_xbmc_json(updateCommand, host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD)
+            request = self._send_to_kodi_json(updateCommand, host, sickbeard.KODI_USERNAME, sickbeard.KODI_PASSWORD)
 
             if not request:
-                logger.log(u"XBMC Full Library update failed on: " + host, logger.ERROR)
+                logger.log(u"KODI Full Library update failed on: " + host, logger.ERROR)
                 return False
 
         return True
@@ -502,31 +502,31 @@ class XBMCNotifier:
     ##############################################################################
 
     def notify_snatch(self, ep_name):
-        if sickbeard.XBMC_NOTIFY_ONSNATCH:
-            self._notify_xbmc(ep_name, common.notifyStrings[common.NOTIFY_SNATCH])
+        if sickbeard.KODI_NOTIFY_ONSNATCH:
+            self._notify_kodi(ep_name, common.notifyStrings[common.NOTIFY_SNATCH])
 
     def notify_download(self, ep_name):
-        if sickbeard.XBMC_NOTIFY_ONDOWNLOAD:
-            self._notify_xbmc(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD])
+        if sickbeard.KODI_NOTIFY_ONDOWNLOAD:
+            self._notify_kodi(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD])
 
     def notify_subtitle_download(self, ep_name, lang):
-        if sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD:
-            self._notify_xbmc(ep_name + ": " + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
+        if sickbeard.KODI_NOTIFY_ONSUBTITLEDOWNLOAD:
+            self._notify_kodi(ep_name + ": " + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
             
     def notify_git_update(self, new_version = "??"):
-        if sickbeard.USE_XBMC:
+        if sickbeard.USE_KODI:
             update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
             title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
-            self._notify_xbmc(update_text + new_version, title)
+            self._notify_kodi(update_text + new_version, title)
 
     def test_notify(self, host, username, password):
-        return self._notify_xbmc("Testing XBMC notifications from SickRage", "Test Notification", host, username,
+        return self._notify_kodi("Testing KODI notifications from SickRage", "Test Notification", host, username,
                                  password, force=True)
 
     def update_library(self, showName=None):
         """Public wrapper for the update library functions to branch the logic for JSON-RPC or legacy HTTP API
 
-        Checks the XBMC API version to branch the logic to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods.
+        Checks the KODI API version to branch the logic to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods.
         Do the ability of accepting a list of hosts deliminated by comma, only one host is updated, the first to respond with success.
         This is a workaround for SQL backend users as updating multiple clients causes duplicate entries.
         Future plan is to revist how we store the host/ip/username/pw/options so that it may be more flexible.
@@ -539,27 +539,27 @@ class XBMCNotifier:
 
         """
 
-        if sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY:
-            if not sickbeard.XBMC_HOST:
-                logger.log(u"No XBMC hosts specified, check your settings", logger.DEBUG)
+        if sickbeard.USE_KODI and sickbeard.KODI_UPDATE_LIBRARY:
+            if not sickbeard.KODI_HOST:
+                logger.log(u"No KODI hosts specified, check your settings", logger.DEBUG)
                 return False
 
             # either update each host, or only attempt to update until one successful result
             result = 0
-            for host in [x.strip() for x in sickbeard.XBMC_HOST.split(",")]:
+            for host in [x.strip() for x in sickbeard.KODI_HOST.split(",")]:
                 if self._send_update_library(host, showName):
-                    if sickbeard.XBMC_UPDATE_ONLYFIRST:
+                    if sickbeard.KODI_UPDATE_ONLYFIRST:
                         logger.log(u"Successfully updated '" + host + "', stopped sending update library commands.",
                                    logger.DEBUG)
                         return True
                 else:
-                    if sickbeard.XBMC_ALWAYS_ON:
+                    if sickbeard.KODI_ALWAYS_ON:
                         logger.log(
-                            u"Failed to detect XBMC version for '" + host + "', check configuration and try again.",
+                            u"Failed to detect KODI version for '" + host + "', check configuration and try again.",
                             logger.ERROR)
                     result = result + 1
 
-            # needed for the 'update xbmc' submenu command
+            # needed for the 'update kodi' submenu command
             # as it only cares of the final result vs the individual ones
             if result == 0:
                 return True
@@ -567,4 +567,4 @@ class XBMCNotifier:
                 return False
 
 
-notifier = XBMCNotifier
+notifier = KODINotifier
diff --git a/sickbeard/notifiers/nma.py b/sickbeard/notifiers/nma.py
index 00cf65ab49a386d5999f01ea3ade28056069bea5..b097f24e7b4a9c9b6768d45fd9a10ebc8542a41d 100644
--- a/sickbeard/notifiers/nma.py
+++ b/sickbeard/notifiers/nma.py
@@ -58,7 +58,7 @@ class NMA_Notifier:
             logger.log(u'Could not send notification to NotifyMyAndroid', logger.ERROR)
             return False
         else:
-            logger.log(u"NMA: Notification sent to NotifyMyAndroid", logger.MESSAGE)
+            logger.log(u"NMA: Notification sent to NotifyMyAndroid", logger.INFO)
             return True
 
 
diff --git a/sickbeard/notifiers/nmj.py b/sickbeard/notifiers/nmj.py
index 78677859f0e7ba684cb2093ee81fffaefa2d00c7..934fba4acd8416a9996990eb16243cba02f8b8b4 100644
--- a/sickbeard/notifiers/nmj.py
+++ b/sickbeard/notifiers/nmj.py
@@ -171,7 +171,7 @@ class NMJNotifier:
             logger.log(u"Popcorn Hour returned an errorcode: %s" % (result), logger.ERROR)
             return False
         else:
-            logger.log(u"NMJ started background scan", logger.MESSAGE)
+            logger.log(u"NMJ started background scan", logger.INFO)
             return True
 
     def _notifyNMJ(self, host=None, database=None, mount=None, force=False):
diff --git a/sickbeard/notifiers/nmjv2.py b/sickbeard/notifiers/nmjv2.py
index 74323e7e743d525b7a6e35fcb13c32bce76b8d8c..ba6cec2d329d03ac344343972ddd9421fdde2baf 100644
--- a/sickbeard/notifiers/nmjv2.py
+++ b/sickbeard/notifiers/nmjv2.py
@@ -154,7 +154,7 @@ class NMJv2Notifier:
                 logger.log(u"Popcorn Hour returned an error: %s" % (error_messages[index]), logger.ERROR)
                 return False
             else:
-                logger.log(u"NMJv2 started background scan", logger.MESSAGE)
+                logger.log(u"NMJv2 started background scan", logger.INFO)
                 return True
 
     def _notifyNMJ(self, host=None, force=False):
diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py
index 3d9f87175ea9047931f9d19a550b67772a662c6f..840c2e586737f13b5902a8d4ab5cd4389f85efb2 100644
--- a/sickbeard/notifiers/plex.py
+++ b/sickbeard/notifiers/plex.py
@@ -25,15 +25,15 @@ import sickbeard
 from sickbeard import logger
 from sickbeard import common
 from sickbeard.exceptions import ex
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
 
-from sickbeard.notifiers.xbmc import XBMCNotifier
+from sickbeard.notifiers.kodi import KODINotifier
 
 # TODO: switch over to using ElementTree
 from xml.dom import minidom
 
 
-class PLEXNotifier(XBMCNotifier):
+class PLEXNotifier(KODINotifier):
     def _notify_pmc(self, message, title="SickRage", host=None, username=None, password=None, force=False):
         # fill in omitted parameters
         if not host:
@@ -52,7 +52,7 @@ class PLEXNotifier(XBMCNotifier):
             logger.log("Notification for Plex not enabled, skipping this notification", logger.DEBUG)
             return False
 
-        return self._notify_xbmc(message=message, title=title, host=host, username=username, password=password,
+        return self._notify_kodi(message=message, title=title, host=host, username=username, password=password,
                                  force=True)
 
     def notify_snatch(self, ep_name):
@@ -77,7 +77,7 @@ class PLEXNotifier(XBMCNotifier):
         return self._notify_pmc("Testing Plex notifications from SickRage", "Test Notification", host, username,
                                 password, force=True)
 
-    def update_library(self):
+    def update_library(self, showName=None):
         """Handles updating the Plex Media Server host via HTTP API
 
         Plex Media Server currently only supports updating the whole video library and not a specific path.
@@ -93,7 +93,7 @@ class PLEXNotifier(XBMCNotifier):
                 return False
 
             logger.log(u"Updating library for the Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST,
-                       logger.MESSAGE)
+                       logger.INFO)
 
             url = "http://%s/library/sections" % sickbeard.PLEX_SERVER_HOST
             try:
@@ -104,7 +104,7 @@ class PLEXNotifier(XBMCNotifier):
 
             sections = xml_sections.getElementsByTagName('Directory')
             if not sections:
-                logger.log(u"Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE)
+                logger.log(u"Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.INFO)
                 return False
 
             for s in sections:
diff --git a/sickbeard/notifiers/prowl.py b/sickbeard/notifiers/prowl.py
index 54180e4e2cbe197c927e4b681cd642dc7e8facd5..93400e5322b3f479e4a9b6d3b5e15894040c3c88 100644
--- a/sickbeard/notifiers/prowl.py
+++ b/sickbeard/notifiers/prowl.py
@@ -95,7 +95,7 @@ class ProwlNotifier:
         request_status = response.status
 
         if request_status == 200:
-            logger.log(u"Prowl notifications sent.", logger.MESSAGE)
+            logger.log(u"Prowl notifications sent.", logger.INFO)
             return True
         elif request_status == 401:
             logger.log(u"Prowl auth failed: %s" % response.reason, logger.ERROR)
diff --git a/sickbeard/notifiers/pushover.py b/sickbeard/notifiers/pushover.py
index 19dde0d69821004bae0fa8dfb07f9da7d324b5b9..59033726e64ac896a8541b0210103a45d43e1861 100644
--- a/sickbeard/notifiers/pushover.py
+++ b/sickbeard/notifiers/pushover.py
@@ -104,7 +104,7 @@ class PushoverNotifier:
                 logger.log("Pushover API message limit reached - try a different API key", logger.ERROR)
                 return False
 
-        logger.log("Pushover notification successful.", logger.MESSAGE)
+        logger.log("Pushover notification successful.", logger.INFO)
         return True
 
     def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]):
diff --git a/sickbeard/notifiers/trakt.py b/sickbeard/notifiers/trakt.py
index 2c2de376be052919f17082e07109e046af8a304a..37a1bbf089f8c370bc33d9d2e310e42506bb5e42 100644
--- a/sickbeard/notifiers/trakt.py
+++ b/sickbeard/notifiers/trakt.py
@@ -18,8 +18,9 @@
 
 import sickbeard
 from sickbeard import logger
-from lib.trakt import *
-
+from sickbeard.exceptions import ex
+from lib.trakt import TraktAPI
+from lib.trakt.exceptions import traktException, traktServerBusy, traktAuthException
 
 class TraktNotifier:
     """
@@ -46,65 +47,67 @@ class TraktNotifier:
         """
 
         trakt_id = sickbeard.indexerApi(ep_obj.show.indexer).config['trakt_id']
+        trakt_api = TraktAPI(sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
 
         if sickbeard.USE_TRAKT:
-            # URL parameters
-            data = {
-                'title': ep_obj.show.name,
-                'year': ep_obj.show.startyear,
-                'episodes': [{
-                                 'season': ep_obj.season,
-                                 'episode': ep_obj.episode
-                             }]
-            }
-
-            if trakt_id == 'tvdb_id':
-                data[trakt_id] = ep_obj.show.indexerid
-
-            # update library
-            TraktCall("show/episode/library/%API%", self._api(), self._username(), self._password(), data)
-
-            # remove from watchlist
-            if sickbeard.TRAKT_REMOVE_WATCHLIST:
-                TraktCall("show/episode/unwatchlist/%API%", self._api(), self._username(), self._password(), data)
-
-            if sickbeard.TRAKT_REMOVE_SERIESLIST:
+            try:
+                # URL parameters
                 data = {
-                    'shows': [
-                        {
-                            'title': ep_obj.show.name,
-                            'year': ep_obj.show.startyear
-                        }
-                    ]
+                    'title': ep_obj.show.name,
+                    'year': ep_obj.show.startyear,
+                    'episodes': [{
+                                     'season': ep_obj.season,
+                                     'episode': ep_obj.episode
+                                 }]
                 }
 
                 if trakt_id == 'tvdb_id':
-                    data['shows'][trakt_id] = ep_obj.show.indexerid
-
-                TraktCall("show/unwatchlist/%API%", self._api(), self._username(), self._password(), data)
-
-                # Remove all episodes from episode watchlist
-                # Start by getting all episodes in the watchlist
-                watchlist = TraktCall("user/watchlist/episodes.json/%API%/" + sickbeard.TRAKT_USERNAME,
-                                      sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
-
-                # Convert watchlist to only contain current show
-                if watchlist:
-                    for show in watchlist:
-                        if show[trakt_id] == ep_obj.show.indexerid:
-                            data_show = {
-                                'title': show['title'],
-                                trakt_id: show[trakt_id],
-                                'episodes': []
+                    data[trakt_id] = ep_obj.show.indexerid
+
+                # update library
+                trakt_api.traktRequest("show/episode/library/%APIKEY%", data)
+
+                # remove from watchlist
+                if sickbeard.TRAKT_REMOVE_WATCHLIST:
+                    trakt_api.traktRequest("show/episode/unwatchlist/%APIKEY%", data)
+
+                if sickbeard.TRAKT_REMOVE_SERIESLIST:
+                    data = {
+                        'shows': [
+                            {
+                                'title': ep_obj.show.name,
+                                'year': ep_obj.show.startyear
                             }
+                        ]
+                    }
+
+                    if trakt_id == 'tvdb_id':
+                        data['shows'][0][trakt_id] = ep_obj.show.indexerid
+
+                    trakt_api.traktRequest("show/unwatchlist/%APIKEY%", data)
 
-                            # Add series and episode (number) to the array
-                            for episodes in show['episodes']:
-                                ep = {'season': episodes['season'], 'episode': episodes['number']}
-                                data_show['episodes'].append(ep)
+                    # Remove all episodes from episode watchlist
+                    # Start by getting all episodes in the watchlist
+                    watchlist = trakt_api.traktRequest("user/watchlist/episodes.json/%APIKEY%/%USER%")
 
-                            TraktCall("show/episode/unwatchlist/%API%", sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME,
-                                      sickbeard.TRAKT_PASSWORD, data_show)
+                    # Convert watchlist to only contain current show
+                    if watchlist:
+                        for show in watchlist:
+                            if show[trakt_id] == ep_obj.show.indexerid:
+                                data_show = {
+                                    'title': show['title'],
+                                    trakt_id: show[trakt_id],
+                                    'episodes': []
+                                }
+
+                                # Add series and episode (number) to the array
+                                for episodes in show['episodes']:
+                                    ep = {'season': episodes['season'], 'episode': episodes['number']}
+                                    data_show['episodes'].append(ep)
+
+                                trakt_api.traktRequest("show/episode/unwatchlist/%APIKEY%", data_show)
+            except (traktException, traktAuthException, traktServerBusy) as e:
+                logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
 
     def test_notify(self, api, username, password):
         """
@@ -118,21 +121,14 @@ class TraktNotifier:
         Returns: True if the request succeeded, False otherwise
         """
 
-        data = TraktCall("account/test/%API%", api, username, password)
-        if data and data["status"] == "success":
-            return True
-
-    def _username(self):
-        return sickbeard.TRAKT_USERNAME
-
-    def _password(self):
-        return sickbeard.TRAKT_PASSWORD
-
-    def _api(self):
-        return sickbeard.TRAKT_API
+        trakt_api = TraktAPI(api, username, password)
 
-    def _use_me(self):
-        return sickbeard.USE_TRAKT
+        try:
+            if trakt_api.validateAccount():
+                return "Test notice sent successfully to Trakt"
+        except (traktException, traktAuthException, traktServerBusy) as e:
+            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
 
+        return "Test notice failed to Trakt: %s" % ex(e)
 
 notifier = TraktNotifier
diff --git a/sickbeard/nzbSplitter.py b/sickbeard/nzbSplitter.py
index 39d1df639f66309908b4dc4c93cfada34af56850..1cbf10c45c48595200568e3798cca829bff7860b 100644
--- a/sickbeard/nzbSplitter.py
+++ b/sickbeard/nzbSplitter.py
@@ -29,8 +29,6 @@ from sickbeard import encodingKludge as ek
 from sickbeard.exceptions import ex
 
 from name_parser.parser import NameParser, InvalidNameException, InvalidShowException
-from sickbeard.encodingKludge import toUnicode
-
 
 def getSeasonNZBs(name, urlData, season):
     try:
@@ -85,7 +83,7 @@ def createNZBString(fileElements, xmlns):
     for curFile in fileElements:
         rootElement.append(stripNS(curFile, xmlns))
 
-    return xml.etree.ElementTree.tostring(toUnicode(rootElement))
+    return xml.etree.ElementTree.tostring(ek.ss(rootElement))
 
 
 def saveNZB(nzbName, nzbString):
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index 9e74978296bd66cbc9eace354948c207c4c02804..02cf37d193dd87266b476eefbc2b4603585c27bb 100644
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -95,7 +95,7 @@ class PostProcessor(object):
         
         self.version = None
 
-    def _log(self, message, level=logger.MESSAGE):
+    def _log(self, message, level=logger.INFO):
         """
         A wrapper for the internal logger which also keeps track of messages and saves them to a string for later.
 
@@ -1009,8 +1009,8 @@ class PostProcessor(object):
         # send notifications
         notifiers.notify_download(ep_obj._format_pattern('%SN - %Sx%0E - %EN - %QN'))
 
-        # do the library update for XBMC
-        notifiers.xbmc_notifier.update_library(ep_obj.show.name)
+        # do the library update for KODI
+        notifiers.kodi_notifier.update_library(ep_obj.show.name)
 
         # do the library update for Plex
         notifiers.plex_notifier.update_library()
diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py
index 560b975d5d30cf4e5e97b3b8b28cf63d59d4356e..c5da4dbdb52b5bbc12e2cd63cfff47998871f9e2 100644
--- a/sickbeard/processTV.py
+++ b/sickbeard/processTV.py
@@ -95,7 +95,7 @@ def delete_files(processPath, notwantedFiles):
         except OSError, e:
             returnStr += logHelper(u"Unable to delete file " + cur_file + ': ' + str(e.strerror), logger.DEBUG)
 
-def logHelper(logMessage, logLevel=logger.MESSAGE):
+def logHelper(logMessage, logLevel=logger.INFO):
     logger.log(logMessage, logLevel)
     return logMessage + u"\n"
 
diff --git a/sickbeard/properFinder.py b/sickbeard/properFinder.py
index 4f21b5ae62e32c9f22128addd7ace8c804424d0d..d62d2d97e540e50968ad0722ceeb993c460c84b9 100644
--- a/sickbeard/properFinder.py
+++ b/sickbeard/properFinder.py
@@ -74,7 +74,7 @@ class ProperFinder():
 
         # for each provider get a list of the
         origThreadName = threading.currentThread().name
-        providers = [x for x in sickbeard.providers.sortedProviderList() if x.isActive()]
+        providers = [x for x in sickbeard.providers.sortedProviderList(sickbeard.RANDOMIZE_PROVIDERS) if x.isActive()]
         for curProvider in providers:
             threading.currentThread().name = origThreadName + " :: [" + curProvider.name + "]"
 
@@ -136,7 +136,8 @@ class ProperFinder():
             curProper.indexer = parse_result.show.indexer
 
             # populate our Proper instance
-            curProper.season = parse_result.season_number if parse_result.season_number != None else 1
+            curProper.show = parse_result.show
+            curProper.season = parse_result.season_number if parse_result.season_number is not None else 1
             curProper.episode = parse_result.episode_numbers[0]
             curProper.release_group = parse_result.release_group
             curProper.version = parse_result.version
@@ -158,14 +159,14 @@ class ProperFinder():
                                                                                  parse_result.show.rls_ignore_words):
                 logger.log(
                     u"Ignoring " + curProper.name + " based on ignored words filter: " + parse_result.show.rls_ignore_words,
-                    logger.MESSAGE)
+                    logger.INFO)
                 continue
 
             if parse_result.show.rls_require_words and not search.filter_release_name(curProper.name,
                                                                                       parse_result.show.rls_require_words):
                 logger.log(
                     u"Ignoring " + curProper.name + " based on required words filter: " + parse_result.show.rls_require_words,
-                    logger.MESSAGE)
+                    logger.INFO)
                 continue
 
             # check if we actually want this proper (if it's the right quality)
@@ -252,9 +253,11 @@ class ProperFinder():
 
                 # make the result object
                 result = curProper.provider.getResult([epObj])
+                result.show = curProper.show
                 result.url = curProper.url
                 result.name = curProper.name
                 result.quality = curProper.quality
+                result.release_group = curProper.release_group
                 result.version = curProper.version
 
                 # snatch it
diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py
index 3158f9ca0fdaf521b015d45fe11f4a47cbbf8019..72f320a32e1f69700b52126cd660e0372201b582 100755
--- a/sickbeard/providers/__init__.py
+++ b/sickbeard/providers/__init__.py
@@ -45,9 +45,9 @@ import sickbeard
 import generic
 from sickbeard import logger
 from os import sys
+from random import shuffle
 
-
-def sortedProviderList():
+def sortedProviderList(randomize=False):
     initialList = sickbeard.providerList + sickbeard.newznabProviderList + sickbeard.torrentRssProviderList
     providerDict = dict(zip([x.getID() for x in initialList], initialList))
 
@@ -63,6 +63,9 @@ def sortedProviderList():
         if providerDict[curModule] not in newList:
             newList.append(providerDict[curModule])
 
+    if randomize:
+        shuffle(newList)
+
     return newList
 
 
diff --git a/sickbeard/providers/animezb.py b/sickbeard/providers/animezb.py
index 2260227bc07a6bcbda4d37a100a330e3dd7e8422..72c435a43774d6ae4299727455d5e87b8a0cdb6f 100644
--- a/sickbeard/providers/animezb.py
+++ b/sickbeard/providers/animezb.py
@@ -78,28 +78,18 @@ class Animezb(generic.NZBProvider):
 
         logger.log(u"Search url: " + search_url, logger.DEBUG)
 
-        data = self.cache.getRSSFeed(search_url)
-        if not data:
-            return []
-
-        if 'entries' in data:
-
-            items = data.entries
-            results = []
-
-            for curItem in items:
-                (title, url) = self._get_title_and_url(curItem)
-
-                if title and url:
-                    results.append(curItem)
-                else:
-                    logger.log(
-                        u"The data returned from the " + self.name + " is incomplete, this result is unusable",
-                        logger.DEBUG)
+        results = []
+        for curItem in self.cache.getRSSFeed(search_url, items=['entries'])['entries'] or []:
+            (title, url) = self._get_title_and_url(curItem)
 
-            return results
+            if title and url:
+                results.append(curItem)
+            else:
+                logger.log(
+                    u"The data returned from the " + self.name + " is incomplete, this result is unusable",
+                    logger.DEBUG)
 
-        return []
+        return results
 
     def findPropers(self, date=None):
 
@@ -144,12 +134,6 @@ class AnimezbCache(tvcache.TVCache):
 
         logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG)
 
-        data = self.getRSSFeed(rss_url)
-
-        if data and 'entries' in data:
-            return data.entries
-        else:
-            return []
-
+        return self.getRSSFeed(rss_url, items=['entries', 'feed'])
 
 provider = Animezb()
diff --git a/sickbeard/providers/bitsoup.py b/sickbeard/providers/bitsoup.py
index c12c089879e4ef2523c2da4dc16ede2bf66a1ca8..187bc9653a9b48b73309678f7c7da3e4bf799d0d 100644
--- a/sickbeard/providers/bitsoup.py
+++ b/sickbeard/providers/bitsoup.py
@@ -157,7 +157,7 @@ class BitSoupProvider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
@@ -228,6 +228,10 @@ class BitSoupProvider(generic.TorrentProvider):
 
         title, url, id, seeders, leechers = item
 
+        if title:
+            title = u'' + title
+            title = title.replace(' ', '.')
+
         if url:
             url = str(url).replace('&amp;', '&')
 
@@ -246,10 +250,7 @@ class BitSoupProvider(generic.TorrentProvider):
             ' OR (e.status IN (' + ','.join([str(x) for x in Quality.SNATCHED]) + ')))'
         )
 
-        if not sqlResults:
-            return []
-
-        for sqlshow in sqlResults:
+        for sqlshow in sqlResults or []:
             self.show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"]))
             if self.show:
                 curEp = self.show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"]))
@@ -276,7 +277,7 @@ class BitSoupCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 
 provider = BitSoupProvider()
diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py
index 6f16ea65c519f52768c9c9e2f6d1c700a847d8b7..912f3050b2fcc501b9ebc5173293e7734c7c5be7 100644
--- a/sickbeard/providers/btn.py
+++ b/sickbeard/providers/btn.py
@@ -78,6 +78,7 @@ class BTNProvider(generic.TorrentProvider):
 
         self._checkAuth()
 
+        results = []
         params = {}
         apikey = self.api_key
 
@@ -91,7 +92,7 @@ class BTNProvider(generic.TorrentProvider):
         parsedJSON = self._api_call(apikey, params)
         if not parsedJSON:
             logger.log(u"No data returned from " + self.name, logger.ERROR)
-            return []
+            return results
 
         if self._checkAuthFromData(parsedJSON):
 
@@ -120,16 +121,13 @@ class BTNProvider(generic.TorrentProvider):
                     if 'torrents' in parsedJSON:
                         found_torrents.update(parsedJSON['torrents'])
 
-            results = []
             for torrentid, torrent_info in found_torrents.iteritems():
                 (title, url) = self._get_title_and_url(torrent_info)
 
                 if title and url:
                     results.append(torrent_info)
 
-            return results
-
-        return []
+        return results
 
     def _api_call(self, apikey, params={}, results_per_page=1000, offset=0):
 
@@ -313,7 +311,7 @@ class BTNCache(tvcache.TVCache):
                 logger.WARNING)
             seconds_since_last_update = 86400
 
-        return self.provider._doSearch(search_params=None, age=seconds_since_last_update)
+        return {'entries': self.provider._doSearch(search_params=None, age=seconds_since_last_update)}
 
 
 provider = BTNProvider()
diff --git a/sickbeard/providers/ezrss.py b/sickbeard/providers/ezrss.py
index 832cdd90c32ade287520a05fbcef693c5dff3adb..38a2d56c5377db7a9537e212334e80504d9cd370 100644
--- a/sickbeard/providers/ezrss.py
+++ b/sickbeard/providers/ezrss.py
@@ -55,8 +55,10 @@ class EZRSSProvider(generic.TorrentProvider):
 
     def getQuality(self, item, anime=False):
 
-        filename = item.filename
-        quality = Quality.sceneQuality(filename, anime)
+        try:
+            quality = Quality.sceneQuality(item.filename, anime)
+        except:
+            quality = Quality.UNKNOWN
 
         return quality
 
@@ -120,37 +122,28 @@ class EZRSSProvider(generic.TorrentProvider):
 
         logger.log(u"Search string: " + search_url, logger.DEBUG)
 
-        data = self.cache.getRSSFeed(search_url)
-
-        if not data:
-            return []
-
-        items = data.entries
-
         results = []
-        for curItem in items:
+        for curItem in self.cache.getRSSFeed(search_url, items=['entries'])['entries'] or []:
 
             (title, url) = self._get_title_and_url(curItem)
 
             if title and url:
                 logger.log(u"RSS Feed provider: [" + self.name + "] Attempting to add item to cache: " + title, logger.DEBUG)
                 results.append(curItem)
-            else:
-                logger.log(
-                    u"The XML returned from the " + self.name + " RSS feed is incomplete, this result is unusable",
-                    logger.ERROR)
 
         return results
 
     def _get_title_and_url(self, item):
         (title, url) = generic.TorrentProvider._get_title_and_url(self, item)
 
-        filename = item.filename
-        if filename:
-            new_title = self._extract_name_from_filename(filename)
-            if new_title:
-                title = new_title
-                logger.log(u"Extracted the name " + title + " from the torrent link", logger.DEBUG)
+        try:
+            new_title = self._extract_name_from_filename(item.filename)
+        except:
+            new_title = None
+
+        if new_title:
+            title = new_title
+            logger.log(u"Extracted the name " + title + " from the torrent link", logger.DEBUG)
 
         return (title, url)
 
@@ -179,11 +172,6 @@ class EZRSSCache(tvcache.TVCache):
         rss_url = self.provider.url + 'feed/'
         logger.log(self.provider.name + " cache update URL: " + rss_url, logger.DEBUG)
 
-        data = self.getRSSFeed(rss_url)
-
-        if data and 'entries' in data:
-            return data.entries
-        else:
-            return []
+        return self.getRSSFeed(rss_url, items=['entries', 'feed'])
 
 provider = EZRSSProvider()
diff --git a/sickbeard/providers/fanzub.py b/sickbeard/providers/fanzub.py
index 3f590778ffbf8202e4d273cabbdf07d727e60839..b1fa3e1de1c5568fab847af5b359d6b7209edebb 100644
--- a/sickbeard/providers/fanzub.py
+++ b/sickbeard/providers/fanzub.py
@@ -73,28 +73,18 @@ class Fanzub(generic.NZBProvider):
 
         logger.log(u"Search url: " + search_url, logger.DEBUG)
 
-        data = self.cache.getRSSFeed(search_url)
-        if not data:
-            return []
-
-        if 'entries' in data:
-
-            items = data.entries
-            results = []
-
-            for curItem in items:
-                (title, url) = self._get_title_and_url(curItem)
-
-                if title and url:
-                    results.append(curItem)
-                else:
-                    logger.log(
-                        u"The data returned from the " + self.name + " is incomplete, this result is unusable",
-                        logger.DEBUG)
+        results = []
+        for curItem in self.cache.getRSSFeed(search_url, items=['entries'])['entries'] or []:
+            (title, url) = self._get_title_and_url(curItem)
 
-            return results
+            if title and url:
+                results.append(curItem)
+            else:
+                logger.log(
+                    u"The data returned from the " + self.name + " is incomplete, this result is unusable",
+                    logger.DEBUG)
 
-        return []
+        return results
 
     def findPropers(self, date=None):
 
@@ -139,12 +129,6 @@ class FanzubCache(tvcache.TVCache):
 
         logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG)
 
-        data = self.getRSSFeed(rss_url)
-
-        if data and 'entries' in data:
-            return data.entries
-        else:
-            return []
-
+        return self.getRSSFeed(rss_url, items=['entries', 'feed'])
 
 provider = Fanzub()
diff --git a/sickbeard/providers/freshontv.py b/sickbeard/providers/freshontv.py
index 121bb826da91db77e83f055432279a3ddc291528..6c82c067d8425bdca375ee78a1f6333ca83284bd 100755
--- a/sickbeard/providers/freshontv.py
+++ b/sickbeard/providers/freshontv.py
@@ -90,9 +90,7 @@ class FreshOnTVProvider(generic.TorrentProvider):
             return True
 
         if self._uid and self._hash:
-
            requests.utils.add_dict_to_cookiejar(self.session.cookies, self.cookies)
-
         else:
             login_params = {'username': self.username,
                             'password': self.password,
@@ -112,17 +110,20 @@ class FreshOnTVProvider(generic.TorrentProvider):
                logger.log(u'Invalid username or password for ' + self.name + ' Check your settings', logger.ERROR)
                return False
 
-            if requests.utils.dict_from_cookiejar(self.session.cookies)['uid'] and requests.utils.dict_from_cookiejar(self.session.cookies)['pass']:
-                    self._uid = requests.utils.dict_from_cookiejar(self.session.cookies)['uid']
-                    self._hash = requests.utils.dict_from_cookiejar(self.session.cookies)['pass']
+            try:
+                if requests.utils.dict_from_cookiejar(self.session.cookies)['uid'] and requests.utils.dict_from_cookiejar(self.session.cookies)['pass']:
+                        self._uid = requests.utils.dict_from_cookiejar(self.session.cookies)['uid']
+                        self._hash = requests.utils.dict_from_cookiejar(self.session.cookies)['pass']
 
-                    self.cookies = {'uid': self._uid,
-                                    'pass': self._hash
-                    }
-                    return True
-            else:
-                    logger.log(u'Unable to obtain cookie for FreshOnTV', logger.ERROR)
-                    return False
+                        self.cookies = {'uid': self._uid,
+                                        'pass': self._hash
+                        }
+                        return True
+            except:
+                pass
+
+            logger.log(u'Unable to obtain cookie for FreshOnTV', logger.ERROR)
+            return False
 
     def _get_season_search_strings(self, ep_obj):
 
@@ -180,7 +181,7 @@ class FreshOnTVProvider(generic.TorrentProvider):
         freeleech = '3' if self.freeleech else '0'
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
@@ -260,6 +261,10 @@ class FreshOnTVProvider(generic.TorrentProvider):
 
         title, url, id, seeders, leechers = item
 
+        if title:
+            title = u'' + title
+            title = title.replace(' ', '.')
+
         if url:
             url = str(url).replace('&amp;', '&')
 
@@ -308,6 +313,6 @@ class FreshOnTVCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 provider = FreshOnTVProvider()
\ No newline at end of file
diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py
index 0ff7ed032df944565205443d0d7baac37c0a3eb1..9069aa55196c910f82e4769cb6bf53e9240729f0 100644
--- a/sickbeard/providers/generic.py
+++ b/sickbeard/providers/generic.py
@@ -171,9 +171,9 @@ class GenericProvider:
                 logger.log(u"Downloading a result from " + self.name + " at " + url)
 
                 if self.providerType == GenericProvider.TORRENT:
-                    logger.log(u"Saved magnet link to " + filename, logger.MESSAGE)
+                    logger.log(u"Saved magnet link to " + filename, logger.INFO)
                 else:
-                    logger.log(u"Saved result to " + filename, logger.MESSAGE)
+                    logger.log(u"Saved result to " + filename, logger.INFO)
 
                 if self._verify_download(filename):
                     return True
@@ -214,7 +214,7 @@ class GenericProvider:
         
         Returns a Quality value obtained from the node's data 
         """
-        (title, url) = self._get_title_and_url(item)  # @UnusedVariable
+        (title, url) = self._get_title_and_url(item)
         quality = Quality.sceneQuality(title, anime)
         return quality
 
@@ -236,18 +236,11 @@ class GenericProvider:
         Returns: A tuple containing two strings representing title and URL respectively
         """
 
-        title = None
-        url = None
-
-        if 'title' in item:
-            title = item.title
-
+        title = item.get('title')
         if title:
-            title = title.replace(' ', '.')
-
-        if 'link' in item:
-            url = item.link
+            title = u'' + title.replace(' ', '.')
 
+        url = item.get('link')
         if url:
             url = url.replace('&amp;', '&')
 
diff --git a/sickbeard/providers/hdbits.py b/sickbeard/providers/hdbits.py
index 19a31cc53cd96f9790ddb9e56a110f0822b8d67b..60431a3b87f3560f2181b3ec9d43c04a181dbfc2 100644
--- a/sickbeard/providers/hdbits.py
+++ b/sickbeard/providers/hdbits.py
@@ -205,16 +205,17 @@ class HDBitsCache(tvcache.TVCache):
         self.minTime = 15
 
     def _getRSSData(self):
-        parsedJSON = self.provider.getURL(self.provider.rss_url, post_data=self.provider._make_post_data_JSON(), json=True)
-
-        if not self.provider._checkAuthFromData(parsedJSON):
-            return []
+        results = []
 
-        if parsedJSON and 'data' in parsedJSON:
-            return parsedJSON['data']
-        else:
-            return []
+        try:
+            parsedJSON = self.provider.getURL(self.provider.rss_url, post_data=self.provider._make_post_data_JSON(),
+                                              json=True)
 
+            if self.provider._checkAuthFromData(parsedJSON):
+                results = parsedJSON['data']
+        except:
+            pass
 
+        return {'entries': results}
 
 provider = HDBitsProvider()                                                                                                              
diff --git a/sickbeard/providers/hdtorrents.py b/sickbeard/providers/hdtorrents.py
index 2618f174b7d80822a662308887bd7e1b05625380..405981d9e0c9bf18d999ec77c951555de32ef059 100644
--- a/sickbeard/providers/hdtorrents.py
+++ b/sickbeard/providers/hdtorrents.py
@@ -179,7 +179,7 @@ class HDTorrentsProvider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
@@ -342,7 +342,7 @@ class HDTorrentsCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': []}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 
 provider = HDTorrentsProvider()
diff --git a/sickbeard/providers/iptorrents.py b/sickbeard/providers/iptorrents.py
index cab576ca3645b32ba9e989d25e8bf980f0962716..09f4a45c62cefe1960f21107ca92704dbf84f809 100644
--- a/sickbeard/providers/iptorrents.py
+++ b/sickbeard/providers/iptorrents.py
@@ -158,13 +158,15 @@ class IPTorrentsProvider(generic.TorrentProvider):
         freeleech = '&free=on' if self.freeleech else ''
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
+                if isinstance(search_string, unicode):
+                    search_string = unidecode(search_string)
 
                 # URL with 50 tv-show results, or max 150 if adjusted in IPTorrents profile
-                searchURL = self.urls['search'] % (self.categorie, freeleech, unidecode(search_string))
+                searchURL = self.urls['search'] % (self.categorie, freeleech, search_string)
                 searchURL += ';o=seeders' if mode != 'RSS' else ''
 
                 logger.log(u"" + self.name + " search page URL: " + searchURL, logger.DEBUG)
@@ -279,7 +281,7 @@ class IPTorrentsCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 
 provider = IPTorrentsProvider()
diff --git a/sickbeard/providers/kat.py b/sickbeard/providers/kat.py
index 840e71069942602a3435bb15b0e3d085f4bd7d4a..cf21d5b676cdb2153bef1841190cdde0e1c26aae 100644
--- a/sickbeard/providers/kat.py
+++ b/sickbeard/providers/kat.py
@@ -61,8 +61,8 @@ class KATProvider(generic.TorrentProvider):
 
         self.cache = KATCache(self)
 
-        self.urls = ['http://kickass.to/', 'http://katproxy.com/', 'http://www.kickmirror.com/']
-        self.url = 'https://kickass.to/'
+        self.urls = ['http://kickass.so/', 'http://katproxy.com/', 'http://www.kickass.to/']
+        self.url = self.urls[0]        
 
     def isEnabled(self):
         return self.enabled
@@ -226,69 +226,70 @@ class KATProvider(generic.TorrentProvider):
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
+                if isinstance(search_string, unicode):
+                    search_string = unidecode(search_string)
 
+                entries = []
                 for url in self.urls:
                     if mode != 'RSS':
-                        searchURL = url + 'usearch/%s/?field=seeders&sorder=desc' % (urllib.quote(unidecode(search_string)))
-                        logger.log(u"Search string: " + searchURL, logger.DEBUG)
+                        searchURL = url + 'usearch/%s/?field=seeders&sorder=desc&rss=1' % urllib.quote(search_string)
                     else:
-                        searchURL = url + 'tv/?field=time_add&sorder=desc'
-                        logger.log(u"KAT cache update URL: " + searchURL, logger.DEBUG)
+                        searchURL = url + 'tv/?field=time_add&sorder=desc&rss=1'
 
-                    html = self.getURL(searchURL)
-                    if html:
-                        self.url = url
-                        break
+                    logger.log(u"Search string: " + searchURL, logger.DEBUG)
 
-                if not html:
-                    continue
+                    entries = self.cache.getRSSFeed(searchURL, items=['entries', 'feed'])['entries']
+                    if entries:
+                        break
 
                 try:
-                    with BS4Parser(html, features=["html5lib", "permissive"]) as soup:
-                        torrent_table = soup.find('table', attrs={'class': 'data'})
-                        torrent_rows = torrent_table.find_all('tr') if torrent_table else []
-
-                        #Continue only if one Release is found
-                        if len(torrent_rows) < 2:
-                            logger.log(u"The data returned from " + self.name + " does not contain any torrents",
-                                       logger.WARNING)
+                    for item in entries or []:
+                        try:
+                            link = item['link']
+                            id = item['guid']
+                            title = item['title']
+                            url = item['torrent_magneturi']
+                            verified = bool(item['torrent_verified'] or 0)
+                            seeders = int(item['torrent_seeds'])
+                            leechers = int(item['torrent_peers'])
+                            size = int(item['torrent_contentlength'])
+                        except (AttributeError, TypeError):
+                            continue
+
+                        if mode != 'RSS' and (seeders < self.minseed or leechers < self.minleech):
                             continue
 
-                        for tr in torrent_rows[1:]:
+                        if self.confirmed and not verified:
+                            logger.log(
+                                u"KAT Provider found result " + title + " but that doesn't seem like a verified result so I'm ignoring it",
+                                logger.DEBUG)
+                            continue
+
+                        #Check number video files = episode in season and find the real Quality for full season torrent analyzing files in torrent
+                        if mode == 'Season' and search_mode == 'sponly':
+                            ep_number = int(epcount / len(set(allPossibleShowNames(self.show))))
+                            title = self._find_season_quality(title, link, ep_number)
+
+                        if not title or not url:
+                            continue
+
+                        try:
+                            pubdate = datetime.datetime(*item['published_parsed'][0:6])
+                        except AttributeError:
                             try:
-                                link = urlparse.urljoin(self.url,
-                                                        (tr.find('div', {'class': 'torrentname'}).find_all('a')[1])['href'])
-                                id = tr.get('id')[-7:]
-                                title = (tr.find('div', {'class': 'torrentname'}).find_all('a')[1]).text \
-                                        or (tr.find('div', {'class': 'torrentname'}).find_all('a')[2]).text
-                                url = tr.find('a', 'imagnet')['href']
-                                verified = True if tr.find('a', 'iverify') else False
-                                trusted = True if tr.find('img', {'alt': 'verified'}) else False
-                                seeders = int(tr.find_all('td')[-2].text)
-                                leechers = int(tr.find_all('td')[-1].text)
-                            except (AttributeError, TypeError):
-                                continue
-
-                            if mode != 'RSS' and (seeders < self.minseed or leechers < self.minleech):
-                                continue
-
-                            if self.confirmed and not verified:
-                                logger.log(
-                                    u"KAT Provider found result " + title + " but that doesn't seem like a verified result so I'm ignoring it",
-                                    logger.DEBUG)
-                                continue
-
-                            #Check number video files = episode in season and find the real Quality for full season torrent analyzing files in torrent
-                            if mode == 'Season' and search_mode == 'sponly':
-                                ep_number = int(epcount / len(set(allPossibleShowNames(self.show))))
-                                title = self._find_season_quality(title, link, ep_number)
-
-                            if not title or not url:
-                                continue
-
-                            item = title, url, id, seeders, leechers
-
-                            items[mode].append(item)
+                                pubdate = datetime.datetime(*item['updated_parsed'][0:6])
+                            except AttributeError:
+                                try:
+                                    pubdate = datetime.datetime(*item['created_parsed'][0:6])
+                                except AttributeError:
+                                    try:
+                                        pubdate = datetime.datetime(*item['date'][0:6])
+                                    except AttributeError:
+                                        pubdate = datetime.datetime.today()
+
+                        item = title, url, id, seeders, leechers, size, pubdate
+
+                        items[mode].append(item)
 
                 except Exception, e:
                     logger.log(u"Failed to parsing " + self.name + " Traceback: " + traceback.format_exc(),
@@ -303,7 +304,7 @@ class KATProvider(generic.TorrentProvider):
 
     def _get_title_and_url(self, item):
 
-        title, url, id, seeders, leechers = item
+        title, url, id, seeders, leechers, size, pubdate = item
 
         if title:
             title = u'' + title
@@ -339,7 +340,9 @@ class KATProvider(generic.TorrentProvider):
 
                 for item in self._doSearch(searchString[0]):
                     title, url = self._get_title_and_url(item)
-                    results.append(classes.Proper(title, url, datetime.datetime.today(), self.show))
+                    pubdate = item[6]
+
+                    results.append(classes.Proper(title, url, pubdate, self.show))
 
         return results
 
@@ -352,12 +355,11 @@ class KATCache(tvcache.TVCache):
 
         tvcache.TVCache.__init__(self, provider)
 
-        # only poll ThePirateBay every 10 minutes max
+        # only poll KickAss every 10 minutes max
         self.minTime = 20
 
     def _getRSSData(self):
         search_params = {'RSS': ['rss']}
-        return self.provider._doSearch(search_params)
-
+        return {'entries': self.provider._doSearch(search_params)}
 
 provider = KATProvider()
diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py
index eb081f2fda33aba70d90bfd994263df7433acebc..8ce32b264fee922d990de611f80be617de58611e 100755
--- a/sickbeard/providers/newznab.py
+++ b/sickbeard/providers/newznab.py
@@ -15,6 +15,7 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
+import traceback
 
 import urllib
 import time
@@ -119,14 +120,14 @@ class NewznabProvider(generic.NZBProvider):
             params['apikey'] = self.key
 
         try:
-            categories = self.getURL("%s/api" % (self.url), params=params, timeout=10)
+            xml_categories = self.getURL("%s/api" % (self.url), params=params, timeout=10, json=True)
         except:
             logger.log(u"Error getting html for [%s]" % 
                     ("%s/api?%s" % (self.url, '&'.join("%s=%s" % (x,y) for x,y in params.items())) ), logger.DEBUG)
             return (False, return_categories, "Error getting html for [%s]" % 
                     ("%s/api?%s" % (self.url, '&'.join("%s=%s" % (x,y) for x,y in params.items()) )))
         
-        xml_categories = helpers.parse_xml(categories)
+        #xml_categories = helpers.parse_xml(categories)
         
         if not xml_categories:
             logger.log(u"Error parsing xml for [%s]" % (self.name),
@@ -237,25 +238,28 @@ class NewznabProvider(generic.NZBProvider):
 
     def _checkAuthFromData(self, data):
 
-        if data is None:
-            return self._checkAuth()
-
-        if 'error' in data.feed:
-            code = data.feed['error']['code']
-
-            if code == '100':
-                raise AuthException("Your API key for " + self.name + " is incorrect, check your config.")
-            elif code == '101':
-                raise AuthException("Your account on " + self.name + " has been suspended, contact the administrator.")
-            elif code == '102':
-                raise AuthException(
-                    "Your account isn't allowed to use the API on " + self.name + ", contact the administrator")
-            else:
-                logger.log(u"Unknown error given from " + self.name + ": " + data.feed['error']['description'],
-                           logger.ERROR)
-                return False
+        try:
+            data['feed']
+            data['entries']
+        except:return self._checkAuth()
 
-        return True
+        try:
+            err_code = int(data['feed']['error']['code'])
+            err_desc = data['feed']['error']['description']
+            if not err_code or err_desc:
+                raise
+        except:
+            return True
+
+        if err_code == 100:
+            raise AuthException("Your API key for " + self.name + " is incorrect, check your config.")
+        elif err_code == 101:
+            raise AuthException("Your account on " + self.name + " has been suspended, contact the administrator.")
+        elif err_code == 102:
+            raise AuthException(
+                "Your account isn't allowed to use the API on " + self.name + ", contact the administrator")
+        else:
+            logger.log(u"Unknown error given from " + self.name + ": " + err_desc, logger.ERROR)
 
     def _doSearch(self, search_params, search_mode='eponly', epcount=0, age=0):
 
@@ -282,10 +286,6 @@ class NewznabProvider(generic.NZBProvider):
         if search_params:
             params.update(search_params)
 
-        if 'rid' not in search_params and 'q' not in search_params:
-            logger.log("Error no rid or search term given. Report to forums with a full debug log")
-            return []
-
         if self.needs_auth and self.key:
             params['apikey'] = self.key
 
@@ -295,12 +295,12 @@ class NewznabProvider(generic.NZBProvider):
         while (total >= offset) and (offset < 1000):
             search_url = self.url + 'api?' + urllib.urlencode(params)
             logger.log(u"Search url: " + search_url, logger.DEBUG)
-            data = self.cache.getRSSFeed(search_url)
 
-            if not data or not self._checkAuthFromData(data):
+            data = self.cache.getRSSFeed(search_url, items=['entries', 'feed'])
+            if not self._checkAuthFromData(data):
                 break
 
-            for item in data.entries:
+            for item in data['entries'] or []:
 
                 (title, url) = self._get_title_and_url(item)
 
@@ -314,8 +314,8 @@ class NewznabProvider(generic.NZBProvider):
             # get total and offset attribs
             try:
                 if total == 0:
-                    total = int(data.feed.newznab_response['total'] or 0)
-                offset = int(data.feed.newznab_response['offset'] or 0)
+                    total = int(data['feed'].newznab_response['total'] or 0)
+                offset = int(data['feed'].newznab_response['offset'] or 0)
             except AttributeError:
                 break
 
@@ -380,13 +380,20 @@ class NewznabProvider(generic.NZBProvider):
 
                 (title, url) = self._get_title_and_url(item)
 
-                if item.has_key('published_parsed') and item['published_parsed']:
-                    result_date = item.published_parsed
-                    if result_date:
-                        result_date = datetime.datetime(*result_date[0:6])
-                else:
-                    logger.log(u"Unable to figure out the date for entry " + title + ", skipping it")
-                    continue
+                try:
+                    result_date = datetime.datetime(*item['published_parsed'][0:6])
+                except AttributeError:
+                    try:
+                        result_date = datetime.datetime(*item['updated_parsed'][0:6])
+                    except AttributeError:
+                        try:
+                            result_date = datetime.datetime(*item['created_parsed'][0:6])
+                        except AttributeError:
+                            try:
+                                result_date = datetime.datetime(*item['date'][0:6])
+                            except AttributeError:
+                                logger.log(u"Unable to figure out the date for entry " + title + ", skipping it")
+                                continue
 
                 if not search_date or result_date > search_date:
                     search_result = classes.Proper(title, url, result_date, self.show)
@@ -420,57 +427,13 @@ class NewznabCache(tvcache.TVCache):
 
         logger.log(self.provider.name + " cache update URL: " + rss_url, logger.DEBUG)
 
-        return self.getRSSFeed(rss_url)
+        return self.getRSSFeed(rss_url, items=['entries', 'feed'])
 
     def _checkAuth(self, data):
         return self.provider._checkAuthFromData(data)
 
-    def updateCache(self):
-
-        if self.shouldUpdate() and self._checkAuth(None):
-            data = self._getRSSData()
-
-            # as long as the http request worked we count this as an update
-            if not data:
-                return []
-
-            self.setLastUpdate()
-
-            # clear cache
-            self._clearCache()
-
-            if self._checkAuth(data):
-                items = data.entries
-                cl = []
-                for item in items:
-                    ci = self._parseItem(item)
-                    if ci is not None:
-                        cl.append(ci)
-
-                if len(cl) > 0:
-                    myDB = self._getDB()
-                    myDB.mass_action(cl)
-
-            else:
-                raise AuthException(
-                    u"Your authentication credentials for " + self.provider.name + " are incorrect, check your config")
-
-        return []
-
-    # overwrite method with that parses the rageid from the newznab feed
     def _parseItem(self, item):
-        title = item.title
-        url = item.link
-
-        attrs = item.newznab_attr
-        if not isinstance(attrs, list):
-            attrs = [item.newznab_attr]
-
-        tvrageid = 0
-        for attr in attrs:
-            if attr['name'] == 'tvrageid':
-                tvrageid = int(attr['value'])
-                break
+        title, url = self._get_title_and_url(item)
 
         self._checkItemAuth(title, url)
 
@@ -480,7 +443,11 @@ class NewznabCache(tvcache.TVCache):
                 logger.DEBUG)
             return None
 
-        url = self._translateLinkURL(url)
+        tvrageid = 0
+        for attr in item['newznab_attr'] if isinstance(item['newznab_attr'], list) else [item['newznab_attr']]:
+            if attr['name'] == 'tvrageid':
+                tvrageid = int(attr['value'] or 0)
+                break
 
         logger.log(u"Attempting to add item from RSS to cache: " + title, logger.DEBUG)
         return self._addCacheEntry(title, url, indexer_id=tvrageid)
diff --git a/sickbeard/providers/nextgen.py b/sickbeard/providers/nextgen.py
index f95db2839bd0884436063133e2585b82d6ce8a33..06d61bac0ab9cc7bfb752661dbd63eaec9d7b0c8 100644
--- a/sickbeard/providers/nextgen.py
+++ b/sickbeard/providers/nextgen.py
@@ -190,7 +190,7 @@ class NextGenProvider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
 
@@ -320,7 +320,7 @@ class NextGenCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 
 provider = NextGenProvider()
diff --git a/sickbeard/providers/nyaatorrents.py b/sickbeard/providers/nyaatorrents.py
index 88d93f9db4c6a734dea06b82b5c22d92486214d3..1b10393b63d7ec3a091b1ae38b0a3e8d5d386fe4 100644
--- a/sickbeard/providers/nyaatorrents.py
+++ b/sickbeard/providers/nyaatorrents.py
@@ -51,7 +51,7 @@ class NyaaProvider(generic.TorrentProvider):
         return 'nyaatorrents.png'
 
     def getQuality(self, item, anime=False):
-        title = item.title
+        title = item.get('title')
         quality = Quality.sceneQuality(title, anime)
         return quality
 
@@ -79,32 +79,20 @@ class NyaaProvider(generic.TorrentProvider):
 
         logger.log(u"Search string: " + searchURL, logger.DEBUG)
 
-        data = self.cache.getRSSFeed(searchURL)
-        if not data:
-            return []
-
-        if 'entries' in data:
-            items = data.entries
-
-            results = []
-
-            for curItem in items:
-
-                (title, url) = self._get_title_and_url(curItem)
+        results = []
+        for curItem in self.cache.getRSSFeed(searchURL, items=['entries'])['entries'] or []:
+            (title, url) = self._get_title_and_url(curItem)
 
-                if title and url:
-                    results.append(curItem)
-                else:
-                    logger.log(
-                        u"The data returned from the " + self.name + " is incomplete, this result is unusable",
-                        logger.DEBUG)
+            if title and url:
+                results.append(curItem)
+            else:
+                logger.log(
+                    u"The data returned from the " + self.name + " is incomplete, this result is unusable",
+                    logger.DEBUG)
 
-            return results
-
-        return []
+        return results
 
     def _get_title_and_url(self, item):
-
         return generic.TorrentProvider._get_title_and_url(self, item)
 
     def _extract_name_from_filename(self, filename):
@@ -137,12 +125,6 @@ class NyaaCache(tvcache.TVCache):
 
         logger.log(u"NyaaTorrents cache update URL: " + url, logger.DEBUG)
 
-        data = self.getRSSFeed(url)
-
-        if data and 'entries' in data:
-            return data.entries
-        else:
-            return []
-
+        return self.getRSSFeed(url, items=['entries', 'feed'])
 
 provider = NyaaProvider()
diff --git a/sickbeard/providers/omgwtfnzbs.py b/sickbeard/providers/omgwtfnzbs.py
index 07a7872fcca1b357d52f0073c1d358a71a8fec85..179c498bcb6db22116f8d99a0119a40ba7efb88d 100644
--- a/sickbeard/providers/omgwtfnzbs.py
+++ b/sickbeard/providers/omgwtfnzbs.py
@@ -163,12 +163,12 @@ class OmgwtfnzbsCache(tvcache.TVCache):
         Returns: A tuple containing two strings representing title and URL respectively
         """
 
-        title = item.title if item.title else None
+        title = item.get('title')
         if title:
             title = u'' + title
             title = title.replace(' ', '.')
 
-        url = item.link if item.link else None
+        url = item.get('link')
         if url:
             url = url.replace('&amp;', '&')
 
@@ -184,11 +184,6 @@ class OmgwtfnzbsCache(tvcache.TVCache):
 
         logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG)
 
-        data = self.getRSSFeed(rss_url)
-
-        if data and 'entries' in data:
-            return data.entries
-        else:
-            return []
+        return self.getRSSFeed(rss_url, items=['entries', 'feed'])
 
 provider = OmgwtfnzbsProvider()
diff --git a/sickbeard/providers/rsstorrent.py b/sickbeard/providers/rsstorrent.py
index fe7b5320feaf8171bf1cc9fffed69300a6794ca0..5e56396c50bbc3809040dcd800b93763c1beb89c 100644
--- a/sickbeard/providers/rsstorrent.py
+++ b/sickbeard/providers/rsstorrent.py
@@ -74,20 +74,18 @@ class TorrentRssProvider(generic.TorrentProvider):
 
     def _get_title_and_url(self, item):
 
-        title, url = None, None
-
-        title = item.title
-
+        title = item.get('title')
         if title:
             title = u'' + title
             title = title.replace(' ', '.')
 
-        attempt_list = [lambda: item.torrent_magneturi,
+        attempt_list = [lambda: item.get('torrent_magneturi'),
 
                         lambda: item.enclosures[0].href,
 
-                        lambda: item.link]
+                        lambda: item.get('link')]
 
+        url = None
         for cur_attempt in attempt_list:
             try:
                 url = cur_attempt()
@@ -95,9 +93,9 @@ class TorrentRssProvider(generic.TorrentProvider):
                 continue
 
             if title and url:
-                return (title, url)
+                break
 
-        return (title, url)
+        return title, url
 
     def validateRSS(self):
 
@@ -107,12 +105,11 @@ class TorrentRssProvider(generic.TorrentProvider):
                 if not cookie_validator.match(self.cookies):
                     return (False, 'Cookie is not correctly formatted: ' + self.cookies)
 
-            items = self.cache._getRSSData()
-
-            if not len(items) > 0:
+            data = self.cache._getRSSData()['entries']
+            if not data:
                 return (False, 'No items found in the RSS feed ' + self.url)
 
-            (title, url) = self._get_title_and_url(items[0])
+            (title, url) = self._get_title_and_url(data[0])
 
             if not title:
                 return (False, 'Unable to get title from first item')
@@ -150,7 +147,7 @@ class TorrentRssProvider(generic.TorrentProvider):
         except IOError, e:
             logger.log("Unable to save the file: " + ex(e), logger.ERROR)
             return False
-        logger.log(u"Saved custom_torrent html dump " + dumpName + " ", logger.MESSAGE)
+        logger.log(u"Saved custom_torrent html dump " + dumpName + " ", logger.INFO)
         return True
 
     def seedRatio(self):
@@ -169,9 +166,4 @@ class TorrentRssCache(tvcache.TVCache):
         if self.provider.cookies:
             request_headers = {'Cookie': self.provider.cookies}
 
-        data = self.getRSSFeed(self.provider.url, request_headers=request_headers)
-
-        if data and 'entries' in data:
-            return data.entries
-        else:
-            return []
+        return self.getRSSFeed(self.provider.url, request_headers=request_headers, items=['entries', 'feed'])
\ No newline at end of file
diff --git a/sickbeard/providers/scc.py b/sickbeard/providers/scc.py
index 08fd81eccc10be43b3e5b070cda91847ca516408..a713ca5602c8d8875cbd75b412517e51908b2b52 100644
--- a/sickbeard/providers/scc.py
+++ b/sickbeard/providers/scc.py
@@ -164,7 +164,7 @@ class SCCProvider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         data = []
         searchURLS = []
@@ -305,8 +305,6 @@ class SCCCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
-
-
+        return {'entries': self.provider._doSearch(search_params)}
 
 provider = SCCProvider()
diff --git a/sickbeard/providers/speedcd.py b/sickbeard/providers/speedcd.py
index 4da4cfff009343e6f29350b2fa7936fd16d13bd8..f01973bd3b73f9f091f03c03e74729d7024afc78 100644
--- a/sickbeard/providers/speedcd.py
+++ b/sickbeard/providers/speedcd.py
@@ -151,7 +151,7 @@ class SpeedCDProvider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
@@ -254,9 +254,7 @@ class SpeedCDCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
-
-
+        return {'entries': self.provider._doSearch(search_params)}
 
 provider = SpeedCDProvider()
 
diff --git a/sickbeard/providers/t411.py b/sickbeard/providers/t411.py
index adaf8d862f5bddcf923e282df014c8f48516a9fd..2b8ec2508467efcd61966ddf6142c57e23428e60 100644
--- a/sickbeard/providers/t411.py
+++ b/sickbeard/providers/t411.py
@@ -12,7 +12,7 @@
 # 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.
+# 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/>.
@@ -25,6 +25,8 @@ import sickbeard
 import generic
 
 from lib import requests
+from lib.requests import exceptions
+
 from sickbeard.common import USER_AGENT, Quality, cpu_presets
 from sickbeard import logger
 from sickbeard import tvcache
@@ -34,32 +36,29 @@ from sickbeard import db
 from sickbeard import helpers
 from sickbeard import classes
 from sickbeard.helpers import sanitizeSceneName
+from sickbeard.exceptions import ex
+
 
 class T411Provider(generic.TorrentProvider):
     urls = {'base_url': 'http://www.t411.me/',
-            'search': 'http://www.t411.me/torrents/search/?name=%s&cat=210&subcat=433&search=%s&submit=Recherche',
+            'search': 'http://www.t411.me/torrents/search/?name=%s&cat=210&subcat=%s&search=%s&submit=Recherche',
             'login_page': 'http://www.t411.me/users/login/',
             'download': 'http://www.t411.me/torrents/download/?id=%s',
     }
 
     def __init__(self):
-
         generic.TorrentProvider.__init__(self, "T411")
 
         self.supportsBacklog = True
-
         self.enabled = False
         self.username = None
         self.password = None
         self.ratio = None
 
         self.cache = T411Cache(self)
-
         self.url = self.urls['base_url']
 
-        self.last_login_check = None
-
-        self.login_opener = None
+        self.subcategories = [637, 455, 433]
 
     def isEnabled(self):
         return self.enabled
@@ -68,60 +67,27 @@ class T411Provider(generic.TorrentProvider):
         return 't411.png'
 
     def getQuality(self, item, anime=False):
-
         quality = Quality.sceneQuality(item[0], anime)
         return quality
 
-    def getLoginParams(self):
-        return {
-            'login': self.username,
-            'password': self.password,
-            'remember': '1',
-        }
-
-    def loginSuccess(self, output):
-        if "<span>Ratio: <strong class" in output.text:
-            return True
-        else:
-            return False
-
     def _doLogin(self):
+        login_params = {'login': self.username,
+                        'password': self.password,
+        }
 
-        now = time.time()
+        self.session = requests.Session()
 
-        if self.login_opener and self.last_login_check < (now - 3600):
-            try:
-                output = self.login_opener.open(self.urls['test'])
-                if self.loginSuccess(output):
-                    self.last_login_check = now
-                    return True
-                else:
-                    self.login_opener = None
-            except:
-                self.login_opener = None
+        try:
+            response = self.session.post(self.urls['login_page'], data=login_params, timeout=30, verify=False)
+        except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError), e:
+            logger.log(u'Unable to connect to ' + self.name + ' provider: ' + ex(e), logger.ERROR)
+            return False
 
-        if self.login_opener:
-            return True
+        if not re.search('/users/logout/', response.text.lower()):
+            logger.log(u'Invalid username or password for ' + self.name + ' Check your settings', logger.ERROR)
+            return False
 
-        try:
-            login_params = self.getLoginParams()
-            self.session = requests.Session()
-            self.session.headers.update({'User-Agent': USER_AGENT})
-            data = self.session.get(self.urls['login_page'], verify=False)
-            output = self.session.post(self.urls['login_page'], data=login_params, verify=False)
-            if self.loginSuccess(output):
-                self.last_login_check = now
-                self.login_opener = self.session
-                return True
-
-            error = 'unknown'
-        except:
-            error = traceback.format_exc()
-            self.login_opener = None
-
-        self.login_opener = None
-        logger.log(u'Failed to login:' + str(error), logger.ERROR)
-        return False
+        return True
 
     def _get_season_search_strings(self, ep_obj):
 
@@ -177,7 +143,7 @@ class T411Provider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
 
@@ -187,54 +153,55 @@ class T411Provider(generic.TorrentProvider):
                     search_string2 = ''
                 else:
                     search_string2 = '%40name+' + search_string + '+'
-                    
-                searchURL = self.urls['search'] % (search_string, search_string2)
-                logger.log(u"" + self.name + " search page URL: " + searchURL, logger.DEBUG)
-
-                data = self.getURL(searchURL)
-
-                if not data:
-                    continue
-
-                try:
-                    with BS4Parser(data.decode('iso-8859-1'), features=["html5lib", "permissive"]) as html:
-                        resultsTable = html.find('table', attrs={'class': 'results'})
-
-                        if not resultsTable:
-                            logger.log(u"The Data returned from " + self.name + " do not contains any torrent",
-                                       logger.DEBUG)
-                            continue
-
-                        entries = resultsTable.find("tbody").findAll("tr")
-
-                        if len(entries) > 0:
-                            for result in entries:
-
-                                try:
-                                    link = result.find('a', title=True)
-                                    torrentName = link['title']
-                                    torrent_name = str(torrentName)
-                                    torrentId = result.find_all('td')[2].find_all('a')[0]['href'][1:].replace('torrents/nfo/?id=','')
-                                    torrent_download_url = (self.urls['download'] % torrentId).encode('utf8')
-                                except (AttributeError, TypeError):
-                                    continue
-
-                                if not torrent_name or not torrent_download_url:
-                                    continue
-
-                                item = torrent_name, torrent_download_url
-                                logger.log(u"Found result: " + torrent_name + " (" + torrent_download_url + ")", logger.DEBUG)
-                                items[mode].append(item)
-
-                        else:
-                            logger.log(u"The Data returned from " + self.name + " do not contains any torrent",
-                                       logger.WARNING)
-                            continue
-
-                except Exception, e:
-                    logger.log(u"Failed parsing " + self.name + " Traceback: " + traceback.format_exc(),
-                               logger.ERROR)
 
+                for sc in self.subcategories:
+                    searchURL = self.urls['search'] % (search_string, sc, search_string2)
+                    logger.log(u"" + self.name + " search page URL: " + searchURL, logger.DEBUG)
+
+                    data = self.getURL(searchURL)
+                    if not data:
+                        continue
+
+                    try:
+                        with BS4Parser(data, features=["html5lib", "permissive"]) as html:
+                            resultsTable = html.find('table', attrs={'class': 'results'})
+
+                            if not resultsTable:
+                                logger.log(u"The Data returned from " + self.name + " do not contains any torrent",
+                                           logger.DEBUG)
+                                continue
+
+                            entries = resultsTable.find("tbody").findAll("tr")
+
+                            if len(entries) > 0:
+                                for result in entries:
+
+                                    try:
+                                        link = result.find('a', title=True)
+                                        torrentName = link['title']
+                                        torrent_name = str(torrentName)
+                                        torrentId = result.find_all('td')[2].find_all('a')[0]['href'][1:].replace(
+                                            'torrents/nfo/?id=', '')
+                                        torrent_download_url = (self.urls['download'] % torrentId).encode('utf8')
+                                    except (AttributeError, TypeError):
+                                        continue
+
+                                    if not torrent_name or not torrent_download_url:
+                                        continue
+
+                                    item = torrent_name, torrent_download_url
+                                    logger.log(u"Found result: " + torrent_name + " (" + torrent_download_url + ")",
+                                               logger.DEBUG)
+                                    items[mode].append(item)
+
+                            else:
+                                logger.log(u"The Data returned from " + self.name + " do not contains any torrent",
+                                           logger.WARNING)
+                                continue
+
+                    except Exception, e:
+                        logger.log(u"Failed parsing " + self.name + " Traceback: " + traceback.format_exc(),
+                                   logger.ERROR)
             results += items[mode]
 
         return results
@@ -286,15 +253,14 @@ class T411Provider(generic.TorrentProvider):
 
 class T411Cache(tvcache.TVCache):
     def __init__(self, provider):
-
         tvcache.TVCache.__init__(self, provider)
 
         # Only poll T411 every 10 minutes max
         self.minTime = 10
 
-    def _getDailyData(self):
+    def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 
 provider = T411Provider()
diff --git a/sickbeard/providers/thepiratebay.py b/sickbeard/providers/thepiratebay.py
index 62c2966a0ee9ebb4d1139c83ff4865c69cc65a36..8e3c3ab7ce2f3aa29ce66709bb5a6fd5106b60e2 100644
--- a/sickbeard/providers/thepiratebay.py
+++ b/sickbeard/providers/thepiratebay.py
@@ -230,9 +230,11 @@ class ThePirateBayProvider(generic.TorrentProvider):
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
+                if isinstance(search_string, unicode):
+                    search_string = unidecode(search_string)
 
                 if mode != 'RSS':
-                    searchURL = self.proxy._buildURL(self.searchurl % (urllib.quote(unidecode(search_string))))
+                    searchURL = self.proxy._buildURL(self.searchurl % (urllib.quote(search_string)))
                 else:
                     searchURL = self.proxy._buildURL(self.url + 'tv/latest/')
 
@@ -340,8 +342,7 @@ class ThePirateBayCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['rss']}
-        return self.provider._doSearch(search_params)
-
+        return {'entries': self.provider._doSearch(search_params)}
 
 class ThePirateBayWebproxy:
     def __init__(self):
diff --git a/sickbeard/providers/tokyotoshokan.py b/sickbeard/providers/tokyotoshokan.py
index 12286b075f57a192cfbf1ed56c7f23fa4f2aee84..f8dde4245f3edf467a64b153868a282069fa1bc1 100644
--- a/sickbeard/providers/tokyotoshokan.py
+++ b/sickbeard/providers/tokyotoshokan.py
@@ -164,12 +164,7 @@ class TokyoToshokanCache(tvcache.TVCache):
 
         logger.log(u"TokyoToshokan cache update URL: " + url, logger.DEBUG)
 
-        data = self.getRSSFeed(url)
-
-        if data and 'entries' in data:
-            return data.entries
-        else:
-            return []
+        return self.getRSSFeed(url, items=['entries', 'feed'])
 
 
 provider = TokyoToshokanProvider()
diff --git a/sickbeard/providers/torrentbytes.py b/sickbeard/providers/torrentbytes.py
index 34342834e9eead67f7b477c69cd15ab29d05b25f..2ef871bf0c97889660fd4f836d657e59b3e46de5 100644
--- a/sickbeard/providers/torrentbytes.py
+++ b/sickbeard/providers/torrentbytes.py
@@ -151,7 +151,7 @@ class TorrentBytesProvider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
@@ -276,7 +276,7 @@ class TorrentBytesCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 
 provider = TorrentBytesProvider()
diff --git a/sickbeard/providers/torrentday.py b/sickbeard/providers/torrentday.py
index 42f8505643fbd9533a0550ecbf9f9671f148bdba..ce9c5cf02f10de0ba27e8cb3384bc3849d4877cd 100644
--- a/sickbeard/providers/torrentday.py
+++ b/sickbeard/providers/torrentday.py
@@ -35,10 +35,10 @@ from sickbeard.helpers import sanitizeSceneName
 
 
 class TorrentDayProvider(generic.TorrentProvider):
-    urls = {'base_url': 'http://www.torrentday.com',
-            'login': 'http://www.torrentday.com/torrents/',
-            'search': 'http://www.torrentday.com/V3/API/API.php',
-            'download': 'http://www.torrentday.com/download.php/%s/%s'
+    urls = {'base_url': 'http://www.td.af',
+            'login': 'http://www.td.af/torrents/',
+            'search': 'http://www.td.af/V3/API/API.php',
+            'download': 'http://www.td.af/download.php/%s/%s'
     }
 
     def __init__(self):
@@ -83,9 +83,7 @@ class TorrentDayProvider(generic.TorrentProvider):
             return True
 
         if self._uid and self._hash:
-
             requests.utils.add_dict_to_cookiejar(self.session.cookies, self.cookies)
-
         else:
 
             login_params = {'username': self.username,
@@ -94,6 +92,9 @@ class TorrentDayProvider(generic.TorrentProvider):
                             'submit.y': 0
             }
 
+            if not self.session:
+                self.session = requests.Session()
+
             try:
                 response = self.session.post(self.urls['login'], data=login_params, timeout=30, verify=False)
             except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError), e:
@@ -108,18 +109,20 @@ class TorrentDayProvider(generic.TorrentProvider):
                 logger.log(u'Invalid username or password for ' + self.name + ', Check your settings!', logger.ERROR)
                 return False
 
-            if requests.utils.dict_from_cookiejar(self.session.cookies)['uid'] and requests.utils.dict_from_cookiejar(self.session.cookies)['pass']:
-                self._uid = requests.utils.dict_from_cookiejar(self.session.cookies)['uid']
-                self._hash = requests.utils.dict_from_cookiejar(self.session.cookies)['pass']
+            try:
+                if requests.utils.dict_from_cookiejar(self.session.cookies)['uid'] and requests.utils.dict_from_cookiejar(self.session.cookies)['pass']:
+                    self._uid = requests.utils.dict_from_cookiejar(self.session.cookies)['uid']
+                    self._hash = requests.utils.dict_from_cookiejar(self.session.cookies)['pass']
 
-                self.cookies = {'uid': self._uid,
-                                'pass': self._hash
-                }
-                return True
+                    self.cookies = {'uid': self._uid,
+                                    'pass': self._hash
+                    }
+                    return True
+            except:
+                pass
 
-            else:
-                logger.log(u'Unable to obtain cookie for TorrentDay', logger.ERROR)
-                return False
+            logger.log(u'Unable to obtain cookie for TorrentDay', logger.ERROR)
+            return False
 
 
     def _get_season_search_strings(self, ep_obj):
@@ -179,7 +182,7 @@ class TorrentDayProvider(generic.TorrentProvider):
         freeleech = '&free=on' if self.freeleech else ''
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
@@ -279,8 +282,6 @@ class TorrentDayCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
-
-
+        return {'entries': self.provider._doSearch(search_params)}
 
 provider = TorrentDayProvider()
diff --git a/sickbeard/providers/torrentleech.py b/sickbeard/providers/torrentleech.py
index 5e89e61c1e468283d3183d76955d5593796a11c3..55be7100b2daaa16e4165920f941b3c18b796005 100644
--- a/sickbeard/providers/torrentleech.py
+++ b/sickbeard/providers/torrentleech.py
@@ -156,7 +156,7 @@ class TorrentLeechProvider(generic.TorrentProvider):
         items = {'Season': [], 'Episode': [], 'RSS': []}
 
         if not self._doLogin():
-            return []
+            return results
 
         for mode in search_params.keys():
             for search_string in search_params[mode]:
@@ -277,7 +277,7 @@ class TorrentLeechCache(tvcache.TVCache):
 
     def _getRSSData(self):
         search_params = {'RSS': ['']}
-        return self.provider._doSearch(search_params)
+        return {'entries': self.provider._doSearch(search_params)}
 
 
 provider = TorrentLeechProvider()
diff --git a/sickbeard/providers/tvtorrents.py b/sickbeard/providers/tvtorrents.py
index 227c194101410d6a698736ea3cf56d958394adec..ed07c43f9aa948948da05e11137bbd6ebead3f19 100644
--- a/sickbeard/providers/tvtorrents.py
+++ b/sickbeard/providers/tvtorrents.py
@@ -54,18 +54,22 @@ class TvTorrentsProvider(generic.TorrentProvider):
         return 'tvtorrents.png'
 
     def _checkAuth(self):
-
         if not self.digest or not self.hash:
             raise AuthException("Your authentication credentials for " + self.name + " are missing, check your config.")
 
         return True
 
     def _checkAuthFromData(self, data):
-        if data.feed.title:
-            description_text = data.feed.title
+        if not (data['entries'] and data['feed']):
+            return self._checkAuth()
+
+        try:
+            title = data['feed']['title']
+        except:
+            return False
 
-        if "User can't be found" in description_text or "Invalid Hash" in description_text:
-            logger.log(u"Incorrect authentication credentials for " + self.name + " : " + str(description_text),
+        if title and ("User can't be found" in title or "Invalid Hash" in title):
+            logger.log(u"Incorrect authentication credentials for " + self.name + " : " + str(title),
                        logger.DEBUG)
             raise AuthException(
                 u"Your authentication credentials for " + self.name + " are incorrect, check your config")
@@ -90,16 +94,9 @@ class TvTorrentsCache(tvcache.TVCache):
         rss_url = self.provider.url + 'RssServlet?digest=' + provider.digest + '&hash=' + provider.hash + '&fname=true&exclude=(' + ignore_regex + ')'
         logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG)
 
-        data = self.getRSSFeed(rss_url)
-
-        if not self.provider._checkAuthFromData(data):
-            return []
-
-        if data and 'entries' in data:
-            return data['entries']
-        else:
-            return []
-
+        return self.getRSSFeed(rss_url, items=['entries', 'feed'])
 
+    def _checkAuth(self, data):
+        return self.provider._checkAuthFromData(data)
 
 provider = TvTorrentsProvider()
diff --git a/sickbeard/providers/womble.py b/sickbeard/providers/womble.py
index e78e8aece7c358aa7df379ba686c38037584097f..d7200e6f67bc1ec305f006d10ffabd2a7b3dbb9c 100644
--- a/sickbeard/providers/womble.py
+++ b/sickbeard/providers/womble.py
@@ -43,42 +43,31 @@ class WombleCache(tvcache.TVCache):
         self.minTime = 15
 
     def updateCache(self):
+        # check if we should update
+        if not self.shouldUpdate():
+            return
 
-        # delete anything older then 7 days
+        # clear cache
         self._clearCache()
 
-        data = None
-
-        if not self.shouldUpdate():
-            return
+        # set updated
+        self.setLastUpdate()
 
         cl = []
         for url in [self.provider.url + 'rss/?sec=tv-sd&fr=false', self.provider.url + 'rss/?sec=tv-hd&fr=false']:
             logger.log(u"Womble's Index cache update URL: " + url, logger.DEBUG)
-            data = self.getRSSFeed(url)
 
-            # As long as we got something from the provider we count it as an update
-            if not data:
-                return []
-
-            # By now we know we've got data and no auth errors, all we need to do is put it in the database
-            for item in data.entries:
+            for item in self.getRSSFeed(url, items=['entries', 'feed'])['entries'] or []:
                 ci = self._parseItem(item)
                 if ci is not None:
                     cl.append(ci)
 
-
-
         if len(cl) > 0:
             myDB = self._getDB()
             myDB.mass_action(cl)
 
-        # set last updated
-        if data:
-            self.setLastUpdate()
-
     def _checkAuth(self, data):
-        return data != 'Invalid Link'
+        return data if data['feed'] and data['feed']['title'] != 'Invalid Link' else None
 
 provider = WombleProvider()
 
diff --git a/sickbeard/rssfeeds.py b/sickbeard/rssfeeds.py
index 6bbc975cb5dff97c6e3b04b0ef2fee5be16fde12..a884862702b30eedbc36f05ca6a09f147fcea72c 100644
--- a/sickbeard/rssfeeds.py
+++ b/sickbeard/rssfeeds.py
@@ -4,53 +4,56 @@ import os
 import urllib
 import urlparse
 import re
+import collections
+
 import sickbeard
 
 from sickbeard import logger
 from sickbeard import encodingKludge as ek
-from contextlib import closing
 from sickbeard.exceptions import ex
-from lib.feedcache import cache
-from shove import Shove
+
+from feedcache.cache import Cache
+from sqliteshelf import SQLiteShelf
 
 
 class RSSFeeds:
     def __init__(self, db_name):
-        self.db_name = ek.ek(os.path.join, sickbeard.CACHE_DIR, 'rss', db_name + '.db')
-        if not os.path.exists(os.path.dirname(self.db_name)):
-            sickbeard.helpers.makeDir(os.path.dirname(self.db_name))
+        try:
+            db_name = ek.ek(os.path.join, sickbeard.CACHE_DIR, 'rss', db_name) + '.db'
+            if not os.path.exists(os.path.dirname(db_name)):
+                sickbeard.helpers.makeDir(os.path.dirname(db_name))
+
+            self.rssDB = SQLiteShelf(db_name)
+        except Exception as e:
+            logger.log(u"RSS error: " + ex(e), logger.DEBUG)
 
     def clearCache(self, age=None):
         try:
-            with closing(Shove('sqlite:///' + self.db_name, compress=True)) as fs:
-                fc = cache.Cache(fs)
-                fc.purge(age)
-        except Exception as e:
-            logger.log(u"RSS error clearing cache: " + ex(e), logger.DEBUG)
+            fc = Cache(self.rssDB).purge(age)
+            fc.purge(age)
+        finally:
+            self.rssDB.close()
 
-    def getFeed(self, url, post_data=None, request_headers=None):
+    def getFeed(self, url, post_data=None, request_headers=None, items=[]):
         parsed = list(urlparse.urlparse(url))
         parsed[2] = re.sub("/{2,}", "/", parsed[2])  # replace two or more / with one
 
         if post_data:
             url += urllib.urlencode(post_data)
 
+        data = dict.fromkeys(items, None)
+
         try:
-            with closing(Shove('sqlite:///' + self.db_name, compress=True)) as fs:
-                fc = cache.Cache(fs)
-                feed = fc.fetch(url, False, False, request_headers)
-
-                if feed:
-                    if 'entries' in feed:
-                        return feed
-                    elif 'error' in feed.feed:
-                        err_code = feed.feed['error']['code']
-                        err_desc = feed.feed['error']['description']
-
-                        logger.log(
-                            u"RSS ERROR:[%s] CODE:[%s]" % (err_desc, err_code), logger.DEBUG)
-                else:
-                    logger.log(u"RSS error loading url: " + url, logger.DEBUG)
+            fc = Cache(self.rssDB)
+            resp = fc.fetch(url, False, False, request_headers)
 
-        except Exception as e:
-            logger.log(u"RSS error: " + ex(e), logger.DEBUG)
\ No newline at end of file
+            for item in items:
+                try:
+                    data[item] = resp[item]
+                except:
+                    continue
+
+        finally:
+            self.rssDB.close()
+
+        return data
\ No newline at end of file
diff --git a/sickbeard/scene_exceptions.py b/sickbeard/scene_exceptions.py
index f41544499f013a6c453563cb527d9c9e4a4738ba..7526854b2382794bee998ef738a4ff86c9afb787 100644
--- a/sickbeard/scene_exceptions.py
+++ b/sickbeard/scene_exceptions.py
@@ -27,7 +27,7 @@ from sickbeard import helpers
 from sickbeard import name_cache
 from sickbeard import logger
 from sickbeard import db
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
 
 exception_dict = {}
 anidb_exception_dict = {}
@@ -179,25 +179,25 @@ def retrieve_exceptions():
                 logger.log(u"Check scene exceptions update failed. Unable to get URL: " + url, logger.ERROR)
                 continue
 
-            else:
-                setLastRefresh(sickbeard.indexerApi(indexer).name)
+            setLastRefresh(sickbeard.indexerApi(indexer).name)
 
-                # each exception is on one line with the format indexer_id: 'show name 1', 'show name 2', etc
-                for cur_line in url_data.splitlines():
-                    cur_line = cur_line.decode('utf-8')
-                    indexer_id, sep, aliases = cur_line.partition(':')  # @UnusedVariable
+            # each exception is on one line with the format indexer_id: 'show name 1', 'show name 2', etc
+            for cur_line in url_data.splitlines():
+                indexer_id, sep, aliases = cur_line.partition(':')  # @UnusedVariable
 
-                    if not aliases:
-                        continue
+                if not aliases:
+                    continue
+
+                indexer_id = int(indexer_id)
 
-                    indexer_id = int(indexer_id)
+                # regex out the list of shows, taking \' into account
+                # alias_list = [re.sub(r'\\(.)', r'\1', x) for x in re.findall(r"'(.*?)(?<!\\)',?", aliases)]
+                alias_list = [{re.sub(r'\\(.)', r'\1', x): -1} for x in re.findall(r"'(.*?)(?<!\\)',?", aliases)]
+                exception_dict[indexer_id] = alias_list
+                del alias_list
 
-                    # regex out the list of shows, taking \' into account
-                    # alias_list = [re.sub(r'\\(.)', r'\1', x) for x in re.findall(r"'(.*?)(?<!\\)',?", aliases)]
-                    alias_list = [{re.sub(r'\\(.)', r'\1', x): -1} for x in re.findall(r"'(.*?)(?<!\\)',?", aliases)]
-                    exception_dict[indexer_id] = alias_list
-                    del alias_list
-                del url_data
+            # cleanup
+            del url_data
 
     # XEM scene exceptions
     _xem_exceptions_fetcher()
@@ -233,9 +233,6 @@ def retrieve_exceptions():
 
             # if this exception isn't already in the DB then add it
             if cur_exception not in existing_exceptions:
-
-                cur_exception = toUnicode(cur_exception)
-
                 myDB.action("INSERT INTO scene_exceptions (indexer_id, show_name, season) VALUES (?,?,?)",
                             [cur_indexer_id, cur_exception, curSeason])
                 changed_exceptions = True
@@ -259,7 +256,7 @@ def update_scene_exceptions(indexer_id, scene_exceptions, season=-1):
     myDB = db.DBConnection('cache.db')
     myDB.action('DELETE FROM scene_exceptions WHERE indexer_id=? and season=?', [indexer_id, season])
 
-    logger.log(u"Updating scene exceptions", logger.MESSAGE)
+    logger.log(u"Updating scene exceptions", logger.INFO)
     
     # A change has been made to the scene exception list. Let's clear the cache, to make this visible
     if indexer_id in exceptionsCache:
@@ -267,8 +264,6 @@ def update_scene_exceptions(indexer_id, scene_exceptions, season=-1):
         exceptionsCache[indexer_id][season] = scene_exceptions
 
     for cur_exception in scene_exceptions:
-        cur_exception = toUnicode(cur_exception)
-
         myDB.action("INSERT INTO scene_exceptions (indexer_id, show_name, season) VALUES (?,?,?)",
                     [indexer_id, cur_exception, season])
 
diff --git a/sickbeard/scene_numbering.py b/sickbeard/scene_numbering.py
index 79786a5379cc07430562442a6266887f319386a6..85b70f80896cb551469c1bc343f1c9b7c805ba23 100644
--- a/sickbeard/scene_numbering.py
+++ b/sickbeard/scene_numbering.py
@@ -493,7 +493,7 @@ def xem_refresh(indexer_id, indexer, force=False):
         try:
             parsedJSON = sickbeard.helpers.getURL(url, json=True)
             if not parsedJSON or parsedJSON == '':
-                logger.log(u'No XEN data for show "%s on %s"' % (indexer_id, sickbeard.indexerApi(indexer).name,), logger.MESSAGE)
+                logger.log(u'No XEN data for show "%s on %s"' % (indexer_id, sickbeard.indexerApi(indexer).name,), logger.INFO)
                 return
 
             if 'success' in parsedJSON['result']:
diff --git a/sickbeard/search.py b/sickbeard/search.py
index decdf50a6de7cef47d948d1bb57b05ece2d15ab3..164e6f759eed310bb30edf556baec5656098fd7a 100644
--- a/sickbeard/search.py
+++ b/sickbeard/search.py
@@ -214,7 +214,7 @@ def pickBestResult(results, show, quality_list=None):
 
         if bwl:
             if not bwl.is_valid(cur_result):
-                logger.log(cur_result.name+" does not match the blacklist or the whitelist, rejecting it. Result: " + bwl.get_last_result_msg(), logger.MESSAGE)
+                logger.log(cur_result.name+" does not match the blacklist or the whitelist, rejecting it. Result: " + bwl.get_last_result_msg(), logger.INFO)
                 continue
 
         if quality_list and cur_result.quality not in quality_list:
@@ -223,12 +223,12 @@ def pickBestResult(results, show, quality_list=None):
 
         if show.rls_ignore_words and filter_release_name(cur_result.name, show.rls_ignore_words):
             logger.log(u"Ignoring " + cur_result.name + " based on ignored words filter: " + show.rls_ignore_words,
-                       logger.MESSAGE)
+                       logger.INFO)
             continue
 
         if show.rls_require_words and not filter_release_name(cur_result.name, show.rls_require_words):
             logger.log(u"Ignoring " + cur_result.name + " based on required words filter: " + show.rls_require_words,
-                       logger.MESSAGE)
+                       logger.INFO)
             continue
 
         if sickbeard.USE_FAILED_DOWNLOADS and failed_history.hasFailed(cur_result.name, cur_result.size,
@@ -339,7 +339,7 @@ def wantedEpisodes(show, fromDate):
     # check through the list of statuses to see if we want any
     wanted = []
     for result in sqlResults:
-        curCompositeStatus = int(result["status"])
+        curCompositeStatus = int(result["status"] or -1)
         curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus)
 
         if bestQualities:
@@ -370,19 +370,16 @@ def searchForNeededEpisodes():
     episodes = []
 
     for curShow in show_list:
-        if curShow.paused:
-            continue
-
-        episodes.extend(wantedEpisodes(curShow, fromDate))
+        if not curShow.paused:
+            episodes.extend(wantedEpisodes(curShow, fromDate))
 
-    providers = [x for x in sickbeard.providers.sortedProviderList() if x.isActive() and x.enable_daily]
+    providers = [x for x in sickbeard.providers.sortedProviderList(sickbeard.RANDOMIZE_PROVIDERS) if x.isActive() and x.enable_daily]
     for curProvider in providers:
+        threads += [threading.Thread(target=curProvider.cache.updateCache, name=origThreadName + " :: [" + curProvider.name + "]")]
 
-        # spawn separate threads for each provider so we don't need to wait for providers with slow network operation
-        threads.append(threading.Thread(target=curProvider.cache.updateCache, name=origThreadName +
-                                                                                   " :: [" + curProvider.name + "]"))
-        # start the thread we just created
-        threads[-1].start()
+    # start the thread we just created
+    for t in threads:
+        t.start()
 
     # wait for all threads to finish
     for t in threads:
@@ -390,20 +387,11 @@ def searchForNeededEpisodes():
 
     for curProvider in providers:
         threading.currentThread().name = origThreadName + " :: [" + curProvider.name + "]"
-
         curFoundResults = curProvider.searchRSS(episodes)
-
         didSearch = True
 
         # pick a single result for each episode, respecting existing results
         for curEp in curFoundResults:
-
-            if curEp.show.paused:
-                logger.log(
-                    u"Show " + curEp.show.name + " is paused, ignoring all RSS items for " + curEp.prettyName(),
-                    logger.DEBUG)
-                continue
-
             # find the best result for the current episode
             bestResult = None
             for curResult in curFoundResults[curEp]:
@@ -446,13 +434,26 @@ def searchProviders(show, episodes, manualSearch=False):
     finalResults = []
 
     didSearch = False
+    threads = []
 
     # build name cache for show
     sickbeard.name_cache.buildNameCache(show)
 
     origThreadName = threading.currentThread().name
 
-    providers = [x for x in sickbeard.providers.sortedProviderList() if x.isActive() and x.enable_backlog]
+    providers = [x for x in sickbeard.providers.sortedProviderList(sickbeard.RANDOMIZE_PROVIDERS) if x.isActive() and x.enable_backlog]
+    for curProvider in providers:
+        threads += [threading.Thread(target=curProvider.cache.updateCache,
+                                     name=origThreadName + " :: [" + curProvider.name + "]")]
+
+    # start the thread we just created
+    for t in threads:
+        t.start()
+
+    # wait for all threads to finish
+    for t in threads:
+        t.join()
+
     for providerNum, curProvider in enumerate(providers):
         if curProvider.anime_only and not show.is_anime:
             logger.log(u"" + str(show.name) + " is not an anime, skiping", logger.DEBUG)
@@ -474,7 +475,6 @@ def searchProviders(show, episodes, manualSearch=False):
                 logger.log(u"Performing season pack search for " + show.name)
 
             try:
-                curProvider.cache.updateCache()
                 searchResults = curProvider.findSearchResults(show, episodes, search_mode, manualSearch)
             except exceptions.AuthException, e:
                 logger.log(u"Authentication error: " + ex(e), logger.ERROR)
diff --git a/sickbeard/searchBacklog.py b/sickbeard/searchBacklog.py
index ee171e8a356ef14443c8e7112bc79b4202e4e6ac..6273a46f59fce2a0be27562499d5c1c6fdc8ef94 100644
--- a/sickbeard/searchBacklog.py
+++ b/sickbeard/searchBacklog.py
@@ -152,7 +152,7 @@ class BacklogSearcher:
         # check through the list of statuses to see if we want any
         wanted = {}
         for result in sqlResults:
-            curCompositeStatus = int(result["status"])
+            curCompositeStatus = int(result["status"] or -1)
             curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus)
 
             if bestQualities:
diff --git a/sickbeard/search_queue.py b/sickbeard/search_queue.py
index 3fa20e75d234fcfebbda0261830ec5e10729bc25..a0ecc4ee3963fcabd71ad77ddc87ec5cccdf53c7 100644
--- a/sickbeard/search_queue.py
+++ b/sickbeard/search_queue.py
@@ -68,10 +68,7 @@ class SearchQueue(generic_queue.GenericQueue):
         for cur_item in self.queue:
             if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and str(cur_item.show.indexerid) == show:
                 ep_obj_list.append(cur_item)
-        
-        if ep_obj_list:
-            return ep_obj_list
-        return False
+        return ep_obj_list
     
     def pause_backlog(self):
         self.min_priority = generic_queue.QueuePriorities.HIGH
diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py
index 736cbf42bf98b42d59f21613b9aed10559065867..357649a0e30335d77f222e694934af703d544577 100644
--- a/sickbeard/show_name_helpers.py
+++ b/sickbeard/show_name_helpers.py
@@ -234,7 +234,7 @@ def isGoodResult(name, show, log=True, season=-1):
 
     all_show_names = allPossibleShowNames(show, season=season)
     showNames = map(sanitizeSceneName, all_show_names) + all_show_names
-    showNames += map(ek.toUnicode, all_show_names)
+    showNames += map(ek.ss, all_show_names)
 
     for curName in set(showNames):
         if not show.is_anime:
diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py
index d0cb2be74616762375271e27eb2384f5a49f4840..1969fa53770d7631cf04fa1380488226a6ec34e1 100644
--- a/sickbeard/show_queue.py
+++ b/sickbeard/show_queue.py
@@ -294,8 +294,8 @@ class QueueItemAdd(ShowQueueItem):
             self.show.paused = self.paused if self.paused != None else False
 
             # set up default new/missing episode status
-            self.show.default_ep_status = self.default_status
             logger.log(u"Setting all episodes to the specified default status: " + str(self.show.default_ep_status))
+            self.show.default_ep_status = self.default_status
 
             # be smartish about this
             if self.show.genre and "talk show" in self.show.genre.lower():
@@ -520,7 +520,7 @@ class QueueItemUpdate(ShowQueueItem):
         try:
             self.show.saveToDB()
         except Exception, e:
-            logger.log(u"Error saving the episode to the database: " + ex(e), logger.ERROR)
+            logger.log(u"Error saving show info to the database: " + ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
 
         # get episode list from DB
@@ -537,7 +537,7 @@ class QueueItemUpdate(ShowQueueItem):
             IndexerEpList = None
 
         foundMissingEps = False
-        if IndexerEpList == None:
+        if IndexerEpList is None:
             logger.log(u"No data returned from " + sickbeard.indexerApi(
                 self.show.indexer).name + ", unable to update this show", logger.ERROR)
         else:
@@ -556,7 +556,7 @@ class QueueItemUpdate(ShowQueueItem):
             for curSeason in DBEpList:
                 for curEpisode in DBEpList[curSeason]:
                     logger.log(u"Permanently deleting episode " + str(curSeason) + "x" + str(
-                        curEpisode) + " from the database", logger.MESSAGE)
+                        curEpisode) + " from the database", logger.INFO)
                     curEp = self.show.getEpisode(curSeason, curEpisode)
                     try:
                         curEp.deleteEpisode()
diff --git a/sickbeard/subtitles.py b/sickbeard/subtitles.py
index 61c37d1feb15095d88175a8dbffd91deb475facd..7079121233840d0cc4bc116ca9c702894cddc8ad 100644
--- a/sickbeard/subtitles.py
+++ b/sickbeard/subtitles.py
@@ -91,7 +91,7 @@ class SubtitlesFinder():
             logger.log(u'Not enough services selected. At least 1 service is required to search subtitles in the background', logger.ERROR)
             return
 
-        logger.log(u'Checking for subtitles', logger.MESSAGE)
+        logger.log(u'Checking for subtitles', logger.INFO)
 
         # get episodes on which we want subtitles
         # criteria is: 
@@ -106,7 +106,7 @@ class SubtitlesFinder():
         myDB = db.DBConnection()
         sqlResults = myDB.select('SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) AS airdate_daydiff FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?) AND ((e.subtitles_searchcount <= 2 AND (? - e.airdate) > 7) OR (e.subtitles_searchcount <= 7 AND (? - e.airdate) <= 7)) AND (e.status IN ('+','.join([str(x) for x in Quality.DOWNLOADED])+') OR (e.status IN ('+','.join([str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER])+') AND e.location != ""))', [today, wantedLanguages(True), today, today])
         if len(sqlResults) == 0:
-            logger.log('No subtitles to download', logger.MESSAGE)
+            logger.log('No subtitles to download', logger.INFO)
             return
         
         rules = self._getRules()
diff --git a/sickbeard/traktChecker.py b/sickbeard/traktChecker.py
index 332028d7cd68fba0dac706216e9715f12d21e1ad..3a9907f921324c6556de78e9da78c4799a54dffb 100644
--- a/sickbeard/traktChecker.py
+++ b/sickbeard/traktChecker.py
@@ -22,16 +22,21 @@ import datetime
 
 import sickbeard
 from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
 from sickbeard import logger
 from sickbeard import helpers
 from sickbeard import search_queue
 from sickbeard.common import SKIPPED, WANTED
+
 from lib.trakt import *
+from trakt.exceptions import traktException, traktAuthException, traktServerBusy
 
 
 class TraktChecker():
+
     def __init__(self):
         self.todoWanted = []
+        self.trakt_api = TraktAPI(sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
 
     def run(self, force=False):
         try:
@@ -51,17 +56,24 @@ class TraktChecker():
             logger.log(traceback.format_exc(), logger.DEBUG)
 
     def findShow(self, indexer, indexerid):
-        library = TraktCall("user/library/shows/all.json/%API%/" + sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
+        traktShow = None
 
-        if not library:
-            logger.log(u"Could not connect to trakt service, aborting library check", logger.ERROR)
-            return
+        try:
+            library = self.trakt_api.traktRequest("user/library/shows/all.json/%APIKEY%/%USER%")
 
-        if not len(library):
-            logger.log(u"No shows found in your library, aborting library update", logger.DEBUG)
-            return
+            if not library:
+                logger.log(u"Could not connect to trakt service, aborting library check", logger.ERROR)
+                return
 
-        return filter(lambda x: int(indexerid) in [int(x['tvdb_id']) or 0, int(x['tvrage_id'])] or 0, library)
+            if not len(library):
+                logger.log(u"No shows found in your library, aborting library update", logger.DEBUG)
+                return
+
+            traktShow = filter(lambda x: int(indexerid) in [int(x['tvdb_id']) or 0, int(x['tvrage_id'])] or 0, library)
+        except (traktException, traktAuthException, traktServerBusy) as e:
+            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
+
+        return traktShow
 
     def syncLibrary(self):
         logger.log(u"Syncing Trakt.tv show library", logger.DEBUG)
@@ -70,17 +82,18 @@ class TraktChecker():
             self.addShowToTraktLibrary(myShow)
 
     def removeShowFromTraktLibrary(self, show_obj):
-        data = {}
         if self.findShow(show_obj.indexer, show_obj.indexerid):
             # URL parameters
-            data['tvdb_id'] = helpers.mapIndexersToShow(show_obj)[1]
-            data['title'] = show_obj.name
-            data['year'] = show_obj.startyear
+            data = {'tvdb_id': helpers.mapIndexersToShow(show_obj)[1], 'title': show_obj.name,
+                    'year': show_obj.startyear}
+
 
-        if len(data):
             logger.log(u"Removing " + show_obj.name + " from trakt.tv library", logger.DEBUG)
-            TraktCall("show/unlibrary/%API%", sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD,
-                      data)
+            try:
+                self.trakt_api.traktRequest("show/unlibrary/%APIKEY%", data)
+            except (traktException, traktAuthException, traktServerBusy) as e:
+                logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
+                pass
 
     def addShowToTraktLibrary(self, show_obj):
         """
@@ -99,15 +112,20 @@ class TraktChecker():
 
         if len(data):
             logger.log(u"Adding " + show_obj.name + " to trakt.tv library", logger.DEBUG)
-            TraktCall("show/library/%API%", sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD,
-                      data)
+
+            try:
+                self.trakt_api.traktRequest("show/library/%APIKEY%", data)
+            except (traktException, traktAuthException, traktServerBusy) as e:
+                logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
+                return
 
     def updateShows(self):
         logger.log(u"Starting trakt show watchlist check", logger.DEBUG)
-        watchlist = TraktCall("user/watchlist/shows.json/%API%/" + sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
 
-        if not watchlist:
-            logger.log(u"Could not connect to trakt service, aborting watchlist update", logger.ERROR)
+        try:
+            watchlist = self.trakt_api.traktRequest("user/watchlist/shows.json/%APIKEY%/%USER%")
+        except (traktException, traktAuthException, traktServerBusy) as e:
+            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
             return
 
         if not len(watchlist):
@@ -138,10 +156,11 @@ class TraktChecker():
         Sets episodes to wanted that are in trakt watchlist
         """
         logger.log(u"Starting trakt episode watchlist check", logger.DEBUG)
-        watchlist = TraktCall("user/watchlist/episodes.json/%API%/" + sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
 
-        if not watchlist:
-            logger.log(u"Could not connect to trakt service, aborting watchlist update", logger.ERROR)
+        try:
+            watchlist = self.trakt_api.traktRequest("user/watchlist/episodes.json/%APIKEY%/%USER%")
+        except (traktException, traktAuthException, traktServerBusy) as e:
+            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
             return
 
         if not len(watchlist):
diff --git a/sickbeard/tv.py b/sickbeard/tv.py
index c75bab1cb6abfccbfc75c49f10de47312a59d4ee..0efde9bbb76b64f3a205c197d7215b776be4d86a 100644
--- a/sickbeard/tv.py
+++ b/sickbeard/tv.py
@@ -82,12 +82,12 @@ class TVShow(object):
         self._imdb_info = {}
         self._quality = int(sickbeard.QUALITY_DEFAULT)
         self._flatten_folders = int(sickbeard.FLATTEN_FOLDERS_DEFAULT)
-        self._status = ""
+        self._status = "Unknown"
         self._airs = ""
         self._startyear = 0
         self._paused = 0
         self._air_by_date = 0
-        self._subtitles = int(sickbeard.SUBTITLES_DEFAULT if sickbeard.SUBTITLES_DEFAULT else 0)
+        self._subtitles = int(sickbeard.SUBTITLES_DEFAULT)
         self._dvdorder = 0
         self._archive_firstmatch = 0
         self._lang = lang
@@ -97,7 +97,7 @@ class TVShow(object):
         self._scene = 0
         self._rls_ignore_words = ""
         self._rls_require_words = ""
-        self._default_ep_status = ""
+        self._default_ep_status = SKIPPED
         self.dirty = True
 
         self._location = ""
@@ -216,19 +216,21 @@ class TVShow(object):
         ep_list = []
         for cur_result in results:
             cur_ep = self.getEpisode(int(cur_result["season"]), int(cur_result["episode"]))
-            if cur_ep:
-                cur_ep.relatedEps = []
-                if cur_ep.location:
-                    # if there is a location, check if it's a multi-episode (share_location > 0) and put them in relatedEps
-                    if cur_result["share_location"] > 0:
-                        related_eps_result = myDB.select(
-                            "SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND location = ? AND episode != ? ORDER BY episode ASC",
-                            [self.indexerid, cur_ep.season, cur_ep.location, cur_ep.episode])
-                        for cur_related_ep in related_eps_result:
-                            related_ep = self.getEpisode(int(cur_related_ep["season"]), int(cur_related_ep["episode"]))
-                            if related_ep not in cur_ep.relatedEps:
-                                cur_ep.relatedEps.append(related_ep)
-                ep_list.append(cur_ep)
+            if not cur_ep:
+                continue
+
+            cur_ep.relatedEps = []
+            if cur_ep.location:
+                # if there is a location, check if it's a multi-episode (share_location > 0) and put them in relatedEps
+                if cur_result["share_location"] > 0:
+                    related_eps_result = myDB.select(
+                        "SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND location = ? AND episode != ? ORDER BY episode ASC",
+                        [self.indexerid, cur_ep.season, cur_ep.location, cur_ep.episode])
+                    for cur_related_ep in related_eps_result:
+                        related_ep = self.getEpisode(int(cur_related_ep["season"]), int(cur_related_ep["episode"]))
+                        if related_ep and related_ep not in cur_ep.relatedEps:
+                            cur_ep.relatedEps.append(related_ep)
+            ep_list.append(cur_ep)
 
         return ep_list
 
@@ -279,16 +281,8 @@ class TVShow(object):
 
     def should_update(self, update_date=datetime.date.today()):
 
-        cur_indexerid = self.indexerid
-
-        # In some situations self.status = None.. need to figure out where that is!
-        if not self.status:
-            self.status = ''
-            logger.log("Status missing for showid: [%s] with status: [%s]" % 
-                       (cur_indexerid, self.status), logger.DEBUG)
-        
-        # if show is not 'Ended' always update (status 'Continuing' or '')
-        if 'Ended' not in self.status:
+        # if show is not 'Ended' always update (status 'Continuing')
+        if 'Unknown' not in self.status and 'Ended' not in self.status:
             return True
 
         # run logic against the current show latest aired and next unaired data to see if we should bypass 'Ended' status
@@ -301,7 +295,7 @@ class TVShow(object):
         myDB = db.DBConnection()
         sql_result = myDB.select(
             "SELECT * FROM tv_episodes WHERE showid = ? AND season > '0' AND airdate > '1' AND status > '1' ORDER BY airdate DESC LIMIT 1",
-            [cur_indexerid])
+            [self.indexerid])
 
         if sql_result:
             last_airdate = datetime.date.fromordinal(sql_result[0]['airdate'])
@@ -311,7 +305,7 @@ class TVShow(object):
         # get next upcoming UNAIRED episode to compare against today + graceperiod
         sql_result = myDB.select(
             "SELECT * FROM tv_episodes WHERE showid = ? AND season > '0' AND airdate > '1' AND status = '1' ORDER BY airdate ASC LIMIT 1",
-            [cur_indexerid])
+            [self.indexerid])
 
         if sql_result:
             next_airdate = datetime.date.fromordinal(sql_result[0]['airdate'])
@@ -369,6 +363,9 @@ class TVShow(object):
             logger.log(str(self.indexerid) + u": Retrieving/creating episode " + str(epResult["season"]) + "x" + str(
                 epResult["episode"]), logger.DEBUG)
             curEp = self.getEpisode(epResult["season"], epResult["episode"])
+            if not curEp:
+                continue
+
             curEp.createMetaFiles()
 
 
@@ -502,6 +499,8 @@ class TVShow(object):
 
             try:
                 curEp = self.getEpisode(curSeason, curEpisode)
+                if not curEp:
+                    raise  exceptions.EpisodeNotFoundException
 
                 # if we found out that the ep is no longer on TVDB then delete it from our database too
                 if deleteEp:
@@ -553,6 +552,8 @@ class TVShow(object):
                     continue
                 try:
                     ep = self.getEpisode(season, episode)
+                    if not ep:
+                        raise exceptions.EpisodeNotFoundException
                 except exceptions.EpisodeNotFoundException:
                     logger.log(
                         str(self.indexerid) + ": " + sickbeard.indexerApi(self.indexer).name + " object for " + str(
@@ -580,6 +581,7 @@ class TVShow(object):
 
         # Done updating save last update date
         self.last_update_indexer = datetime.date.today().toordinal()
+
         self.saveToDB()
 
         return scannedEps
@@ -643,11 +645,13 @@ class TVShow(object):
 
             checkQualityAgain = False
             same_file = False
-            curEp = self.getEpisode(season, episode)
 
-            if curEp == None:
+            curEp = self.getEpisode(season, episode)
+            if not curEp:
                 try:
                     curEp = self.getEpisode(season, episode, file)
+                    if not curEp:
+                        raise exceptions.EpisodeNotFoundException
                 except exceptions.EpisodeNotFoundException:
                     logger.log(str(self.indexerid) + u": Unable to figure out what this file is, skipping",
                                logger.ERROR)
@@ -676,11 +680,13 @@ class TVShow(object):
                 rootEp = curEp
             else:
                 if curEp not in rootEp.relatedEps:
-                    rootEp.relatedEps.append(curEp)
+                    with rootEp.lock:
+                        rootEp.relatedEps.append(curEp)
 
             # if it's a new file then
             if not same_file:
-                curEp.release_name = ''
+                with curEp.lock:
+                    curEp.release_name = ''
 
             # if they replace a file on me I'll make some attempt at re-checking the quality unless I know it's the same file
             if checkQualityAgain and not same_file:
@@ -688,7 +694,8 @@ class TVShow(object):
                 logger.log(u"Since this file has been renamed, I checked " + file + " and found quality " +
                            Quality.qualityStrings[newQuality], logger.DEBUG)
                 if newQuality != Quality.UNKNOWN:
-                    curEp.status = Quality.compositeStatus(DOWNLOADED, newQuality)
+                    with curEp.lock:
+                        curEp.status = Quality.compositeStatus(DOWNLOADED, newQuality)
 
 
             # check for status/quality changes as long as it's a new file
@@ -719,7 +726,7 @@ class TVShow(object):
                 elif oldStatus not in (SNATCHED, SNATCHED_PROPER):
                     newStatus = DOWNLOADED
 
-                if newStatus != None:
+                if newStatus is not None:
                     with curEp.lock:
                         logger.log(u"STATUS: we have an associated file, so setting the status from " + str(
                             curEp.status) + u" to DOWNLOADED/" + str(Quality.statusFromName(file, anime=self.is_anime)),
@@ -735,7 +742,7 @@ class TVShow(object):
 
 
         # creating metafiles on the root should be good enough
-        if sickbeard.USE_FAILED_DOWNLOADS and rootEp is not None:
+        if rootEp:
             with rootEp.lock:
                 rootEp.createMetaFiles()
 
@@ -754,64 +761,38 @@ class TVShow(object):
             logger.log(str(self.indexerid) + ": Unable to find the show in the database")
             return
         else:
-            if not self.indexer:
-                self.indexer = int(sqlResults[0]["indexer"])
+            self.indexer = int(sqlResults[0]["indexer"] or 0)
+
             if not self.name:
                 self.name = sqlResults[0]["show_name"]
             if not self.network:
                 self.network = sqlResults[0]["network"]
             if not self.genre:
                 self.genre = sqlResults[0]["genre"]
-            if self.classification is None:
+            if not self.classification:
                 self.classification = sqlResults[0]["classification"]
 
             self.runtime = sqlResults[0]["runtime"]
 
             self.status = sqlResults[0]["status"]
-            if not self.status:
-                self.status = ""
+            if self.status is None:
+                self.status = "Unknown"
 
             self.airs = sqlResults[0]["airs"]
-            if not self.airs:
+            if self.airs is None:
                 self.airs = ""
 
-            self.startyear = sqlResults[0]["startyear"]
-            if not self.startyear:
-                self.startyear = 0
-
-            self.air_by_date = sqlResults[0]["air_by_date"]
-            if not self.air_by_date:
-                self.air_by_date = 0
-
-            self.anime = sqlResults[0]["anime"]
-            if self.anime == None:
-                self.anime = 0
-
-            self.sports = sqlResults[0]["sports"]
-            if not self.sports:
-                self.sports = 0
-
-            self.scene = sqlResults[0]["scene"]
-            if not self.scene:
-                self.scene = 0
-
-            self.subtitles = sqlResults[0]["subtitles"]
-            if self.subtitles:
-                self.subtitles = 1
-            else:
-                self.subtitles = 0
-
-            self.dvdorder = sqlResults[0]["dvdorder"]
-            if not self.dvdorder:
-                self.dvdorder = 0
-
-            self.archive_firstmatch = sqlResults[0]["archive_firstmatch"]
-            if not self.archive_firstmatch:
-                self.archive_firstmatch = 0
-
-            self.quality = int(sqlResults[0]["quality"])
-            self.flatten_folders = int(sqlResults[0]["flatten_folders"])
-            self.paused = int(sqlResults[0]["paused"])
+            self.startyear = int(sqlResults[0]["startyear"] or 0)
+            self.air_by_date = int(sqlResults[0]["air_by_date"] or 0)
+            self.anime = int(sqlResults[0]["anime"] or 0)
+            self.sports = int(sqlResults[0]["sports"] or 0)
+            self.scene = int(sqlResults[0]["scene"] or 0)
+            self.subtitles = int(sqlResults[0]["subtitles"] or 0)
+            self.dvdorder = int(sqlResults[0]["dvdorder"] or 0)
+            self.archive_firstmatch = int(sqlResults[0]["archive_firstmatch"] or 0)
+            self.quality = int(sqlResults[0]["quality"] or UNKNOWN)
+            self.flatten_folders = int(sqlResults[0]["flatten_folders"] or 0)
+            self.paused = int(sqlResults[0]["paused"] or 0)
 
             try:
                 self.location = sqlResults[0]["location"]
@@ -827,9 +808,7 @@ class TVShow(object):
             self.rls_ignore_words = sqlResults[0]["rls_ignore_words"]
             self.rls_require_words = sqlResults[0]["rls_require_words"]
 
-            self.default_ep_status = sqlResults[0]["default_ep_status"]
-            if not self.default_ep_status:
-                self.default_ep_status = ""
+            self.default_ep_status = int(sqlResults[0]["default_ep_status"] or SKIPPED)
 
             if not self.imdbid:
                 self.imdbid = sqlResults[0]["imdb_id"]
@@ -888,10 +867,13 @@ class TVShow(object):
         if getattr(myEp, 'airs_dayofweek', None) is not None and getattr(myEp, 'airs_time', None) is not None:
             self.airs = myEp["airs_dayofweek"] + " " + myEp["airs_time"]
 
+        if self.airs is None:
+            self.airs = ''
+
         if getattr(myEp, 'firstaired', None) is not None:
             self.startyear = int(str(myEp["firstaired"]).split('-')[0])
 
-        self.status = getattr(myEp, 'status', '')
+        self.status = getattr(myEp, 'status', 'Unknown')
 
     def loadIMDbInfo(self, imdbapi=None):
 
@@ -909,10 +891,13 @@ class TVShow(object):
                      'last_update': ''
         }
 
+        i = imdb.IMDb()
+        if not self.imdbid:
+            self.imdbid = i.title2imdbID(self.name, kind='tv series')
+
         if self.imdbid:
             logger.log(str(self.indexerid) + u": Loading show info from IMDb")
 
-            i = imdb.IMDb()
             imdbTv = i.get_movie(str(re.sub("[^0-9]", "", self.imdbid)))
 
             for key in filter(lambda x: x.replace('_', ' ') in imdbTv.keys(), imdb_info.keys()):
@@ -1072,6 +1057,8 @@ class TVShow(object):
 
             try:
                 curEp = self.getEpisode(season, episode)
+                if not curEp:
+                    raise exceptions.EpisodeDeletedException
             except exceptions.EpisodeDeletedException:
                 logger.log(u"The episode was deleted while we were refreshing it, moving on to the next one",
                            logger.DEBUG)
@@ -1102,37 +1089,36 @@ class TVShow(object):
             else:
                 # the file exists, set its modify file stamp
                 if sickbeard.AIRDATE_EPISODES:
-                    curEp.airdateModifyStamp()
+                    with curEp.lock:
+                        curEp.airdateModifyStamp()
 
         if len(sql_l) > 0:
             myDB = db.DBConnection()
             myDB.mass_action(sql_l)
 
     def downloadSubtitles(self, force=False):
-        # TODO: Add support for force option
         if not ek.ek(os.path.isdir, self._location):
             logger.log(str(self.indexerid) + ": Show dir doesn't exist, can't download subtitles", logger.DEBUG)
             return
+
         logger.log(str(self.indexerid) + ": Downloading subtitles", logger.DEBUG)
 
         try:
-            myDB = db.DBConnection()
-            episodes = myDB.select(
-                "SELECT location FROM tv_episodes WHERE showid = ? AND location NOT LIKE '' ORDER BY season DESC, episode DESC",
-                [self.indexerid])
+            episodes = self.getAllEpisodes(has_location=True)
+            if not len(episodes) > 0:
+                logger.log(str(self.indexerid) + ": No episodes to download subtitles for " + self.name, logger.DEBUG)
+                return
 
-            for episodeLoc in episodes:
-                episode = self.makeEpFromFile(episodeLoc['location'])
-                subtitles = episode.downloadSubtitles(force=force)
-        except Exception as e:
-            logger.log("Error occurred when downloading subtitles: " + traceback.format_exc(), logger.DEBUG)
-            return
+            for episode in episodes:
+                episode.downloadSubtitles(force=force)
 
+        except Exception:
+            logger.log("Error occurred when downloading subtitles: " + traceback.format_exc(), logger.DEBUG)
 
     def saveToDB(self, forceSave=False):
 
         if not self.dirty and not forceSave:
-            logger.log(str(self.indexerid) + u": Not saving show to db - record is not dirty", logger.DEBUG)
+            logger.log(str(self.indexerid) + ": Not saving show to db - record is not dirty", logger.DEBUG)
             return
 
         logger.log(str(self.indexerid) + u": Saving show info to database", logger.DEBUG)
@@ -1188,8 +1174,7 @@ class TVShow(object):
             toReturn += "network: " + self.network + "\n"
         if self.airs:
             toReturn += "airs: " + self.airs + "\n"
-        if self.status:
-            toReturn += "status: " + self.status + "\n"
+        toReturn += "status: " + self.status + "\n"
         toReturn += "startyear: " + str(self.startyear) + "\n"
         if self.genre:
             toReturn += "genre: " + self.genre + "\n"
@@ -1404,7 +1389,7 @@ class TVEpisode(object):
             need_languages = set(sickbeard.SUBTITLES_LANGUAGES) - set(self.subtitles)
             subtitles = subliminal.download_subtitles([self.location], languages=need_languages,
                                                       services=sickbeard.subtitles.getEnabledServiceList(), force=force,
-                                                      multi=True, cache_dir=sickbeard.CACHE_DIR)
+                                                      multi=sickbeard.SUBTITLES_MULTI, cache_dir=sickbeard.CACHE_DIR)
 
             if sickbeard.SUBTITLES_DIR:
                 for video in subtitles:
@@ -1664,8 +1649,7 @@ class TVEpisode(object):
         if getattr(myEp, 'absolute_number', None) is None:
             logger.log(u"This episode (" + self.show.name + " - " + str(season) + "x" + str(
                 episode) + ") has no absolute number on " + sickbeard.indexerApi(
-                self.indexer).name
-                       , logger.DEBUG)
+                self.indexer).name, logger.DEBUG)
         else:
             logger.log(
                 str(self.show.indexerid) + ": The absolute_number for " + str(season) + "x" + str(episode) + " is : " +
@@ -1746,9 +1730,12 @@ class TVEpisode(object):
             # if we don't have the file and the airdate is in the past
             else:
                 if self.status == UNAIRED:
-                    self.status = WANTED
+                    if self.season > 0:
+                        self.status = WANTED
+                    else:
+                        self.status = SKIPPED
 
-                # if we somehow are still UNKNOWN then just use the shows defined default status
+                # if we somehow are still UNKNOWN then just use the shows defined default status or SKIPPED
                 elif self.status == UNKNOWN:
                     self.status = self.show.default_ep_status
 
diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py
index dec8280d6dede8cd76ed79bbb8d08beff85bf66f..e2ba8918372bb528a998cd2a580ed4d41a8b994f 100644
--- a/sickbeard/tvcache.py
+++ b/sickbeard/tvcache.py
@@ -21,6 +21,7 @@ from __future__ import with_statement
 import time
 import datetime
 import itertools
+import traceback
 
 import sickbeard
 
@@ -28,12 +29,13 @@ from sickbeard import db
 from sickbeard import logger
 from sickbeard.common import Quality
 from sickbeard import helpers, show_name_helpers
-from sickbeard.exceptions import MultipleShowObjectsException
+from sickbeard.exceptions import MultipleShowObjectsException, ex
 from sickbeard.exceptions import AuthException
 from sickbeard.rssfeeds import RSSFeeds
 from sickbeard import clients
 from name_parser.parser import NameParser, InvalidNameException, InvalidShowException
-from sickbeard.encodingKludge import toUnicode
+from sickbeard import encodingKludge as ek
+
 
 class CacheDBConnection(db.DBConnection):
     def __init__(self, providerName):
@@ -46,7 +48,7 @@ class CacheDBConnection(db.DBConnection):
                     "CREATE TABLE [" + providerName + "] (name TEXT, season NUMERIC, episodes TEXT, indexerid NUMERIC, url TEXT, time NUMERIC, quality TEXT, release_group TEXT)")
             else:
                 sqlResults = self.select(
-                    "SELECT url, COUNT(url) as count FROM [" + providerName + "] GROUP BY url HAVING count > 1")
+                    "SELECT url, COUNT(url) AS count FROM [" + providerName + "] GROUP BY url HAVING count > 1")
 
                 for cur_dupe in sqlResults:
                     self.action("DELETE FROM [" + providerName + "] WHERE url = ?", [cur_dupe["url"]])
@@ -74,6 +76,7 @@ class CacheDBConnection(db.DBConnection):
             if str(e) != "table lastUpdate already exists":
                 raise
 
+
 class TVCache():
     def __init__(self, provider):
 
@@ -95,48 +98,49 @@ class TVCache():
             myDB.action("DELETE FROM [" + self.providerID + "] WHERE 1")
 
     def _get_title_and_url(self, item):
-        # override this in the provider if daily search has a different data layout to backlog searches
         return self.provider._get_title_and_url(item)
 
     def _getRSSData(self):
-        data = None
-        return data
+        return None
 
-    def _checkAuth(self):
-        return self.provider._checkAuth()
+    def _checkAuth(self, data):
+        return True
 
     def _checkItemAuth(self, title, url):
         return True
 
     def updateCache(self):
-        if self.shouldUpdate() and self._checkAuth():
-            # as long as the http request worked we count this as an update
-            data = self._getRSSData()
-            if not data:
-                return []
+        # check if we should update
+        if not self.shouldUpdate():
+            return
 
-            # clear cache
-            self._clearCache()
+        try:
+            data = self._getRSSData()
+            if self._checkAuth(data):
+                # clear cache
+                self._clearCache()
 
-            # set updated
-            self.setLastUpdate()
+                # set updated
+                self.setLastUpdate()
 
-            # parse data
-            cl = []
-            for item in data:
-                title, url = self._get_title_and_url(item)
-                ci = self._parseItem(title, url)
-                if ci is not None:
-                    cl.append(ci)
+                cl = []
+                for item in data['entries'] or []:
+                    ci = self._parseItem(item)
+                    if ci is not None:
+                        cl.append(ci)
 
-            if len(cl) > 0:
-                myDB = self._getDB()
-                myDB.mass_action(cl)
+                if len(cl) > 0:
+                    myDB = self._getDB()
+                    myDB.mass_action(cl)
 
-        return []
+        except AuthException, e:
+            logger.log(u"Authentication error: " + ex(e), logger.ERROR)
+        except Exception, e:
+            logger.log(u"Error while searching " + self.provider.name + ", skipping: " + ex(e), logger.ERROR)
+            logger.log(traceback.format_exc(), logger.DEBUG)
 
-    def getRSSFeed(self, url, post_data=None, request_headers=None):
-        return RSSFeeds(self.providerID).getFeed(url, post_data, request_headers)
+    def getRSSFeed(self, url, post_data=None, request_headers=None, items=[]):
+        return RSSFeeds(self.providerID).getFeed(url, post_data, request_headers, items)
 
     def _translateTitle(self, title):
         return u'' + title.replace(' ', '.')
@@ -144,7 +148,8 @@ class TVCache():
     def _translateLinkURL(self, url):
         return url.replace('&amp;', '&')
 
-    def _parseItem(self, title, url):
+    def _parseItem(self, item):
+        title, url = self._get_title_and_url(item)
 
         self._checkItemAuth(title, url)
 
@@ -159,8 +164,6 @@ class TVCache():
             logger.log(
                 u"The data returned from the " + self.provider.name + " feed is incomplete, this result is unusable",
                 logger.DEBUG)
-            return None
-
 
     def _getLastUpdate(self):
         myDB = self._getDB()
@@ -232,7 +235,7 @@ class TVCache():
         if not parse_result:
 
             # create showObj from indexer_id if available
-            showObj=None
+            showObj = None
             if indexer_id:
                 showObj = helpers.findCertainShow(sickbeard.showList, indexer_id)
 
@@ -263,7 +266,7 @@ class TVCache():
             # get quality of release
             quality = parse_result.quality
 
-            name = toUnicode(name)
+            name = ek.ss(name)
 
             # get release group
             release_group = parse_result.release_group
@@ -280,10 +283,7 @@ class TVCache():
 
     def searchCache(self, episode, manualSearch=False):
         neededEps = self.findNeededEpisodes(episode, manualSearch)
-        if len(neededEps) > 0:
-            return neededEps[episode]
-        else:
-            return []
+        return neededEps[episode] if len(neededEps) > 0 else []
 
     def listPropers(self, date=None, delimiter="."):
         myDB = self._getDB()
@@ -307,8 +307,8 @@ class TVCache():
         else:
             for epObj in episode:
                 cl.append([
-                    "SELECT * FROM [" + self.providerID + "] WHERE indexerid = ? AND season = ? AND episodes LIKE ? "
-                    "AND quality IN (" + ",".join([str(x) for x in epObj.wantedQuality]) + ")",
+                    "SELECT * FROM [" + self.providerID + "] WHERE indexerid = ? AND season = ? AND episodes LIKE ? AND quality IN (" + ",".join(
+                        [str(x) for x in epObj.wantedQuality]) + ")",
                     [epObj.show.indexerid, epObj.season, "%|" + str(epObj.episode) + "|%"]])
 
             sqlResults = myDB.mass_action(cl, fetchall=True)
diff --git a/sickbeard/ui.py b/sickbeard/ui.py
index 44348b6966a8da7706ecf1009cc7d37eae56d61f..43044c6cf1953d8845005ff3e57904bcc19cf234 100644
--- a/sickbeard/ui.py
+++ b/sickbeard/ui.py
@@ -30,9 +30,6 @@ class Notifications(object):
         self._messages = []
         self._errors = []
 
-    def __del__(self):
-        pass
-
     def message(self, title, message=''):
         """
         Add a regular notification to the queue
@@ -92,9 +89,6 @@ class Notification(object):
         else:
             self._timeout = datetime.timedelta(minutes=1)
 
-    def __del__(self):
-        pass
-
     def is_new(self, remote_ip='127.0.0.1'):
         """
         Returns True if the notification hasn't been displayed to the current client (aka IP address).
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index 8495da2f3882efecc4c6a790c7053bf3ac5e980a..dc4cf28cb1d54f47e435c49a0396f58671345cbd 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -22,13 +22,12 @@ import shutil
 import subprocess
 import re
 import urllib
-import zipfile
 import tarfile
 import stat
 import traceback
 
 import sickbeard
-from sickbeard import helpers, notifiers
+from sickbeard import notifiers
 from sickbeard import ui
 from sickbeard import logger
 from sickbeard.exceptions import ex
@@ -40,29 +39,28 @@ class CheckVersion():
     """
 
     def __init__(self):
-        self.install_type = self.find_install_type()
-
-        if self.install_type == 'win':
-            self.updater = WindowsUpdateManager()
-        elif self.install_type == 'git':
-            self.updater = GitUpdateManager()
-        elif self.install_type == 'source':
-            self.updater = SourceUpdateManager()
-        else:
-            self.updater = None
+        self.updater = None
+
+        if sickbeard.gh:
+            self.install_type = self.find_install_type()
+            if self.install_type == 'git':
+                self.updater = GitUpdateManager()
+            elif self.install_type == 'source':
+                self.updater = SourceUpdateManager()
 
     def run(self, force=False):
-        # set current branch version
-        sickbeard.BRANCH = self.get_branch()
-
-        if self.check_for_new_version(force):
-            if sickbeard.AUTO_UPDATE:
-                logger.log(u"New update found for SickRage, starting auto-updater ...")
-                ui.notifications.message('New update found for SickRage, starting auto-updater')
-                if sickbeard.versionCheckScheduler.action.update():
-                    logger.log(u"Update was successful!")
-                    ui.notifications.message('Update was successful')
-                    sickbeard.events.put(sickbeard.events.SystemEvent.RESTART)
+        if self.updater:
+            # set current branch version
+            sickbeard.BRANCH = self.get_branch()
+
+            if self.check_for_new_version(force):
+                if sickbeard.AUTO_UPDATE:
+                    logger.log(u"New update found for SickRage, starting auto-updater ...")
+                    ui.notifications.message('New update found for SickRage, starting auto-updater')
+                    if sickbeard.versionCheckScheduler.action.update():
+                        logger.log(u"Update was successful!")
+                        ui.notifications.message('Update was successful')
+                        sickbeard.events.put(sickbeard.events.SystemEvent.RESTART)
 
     def find_install_type(self):
         """
@@ -93,37 +91,46 @@ class CheckVersion():
         force: if true the VERSION_NOTIFY setting will be ignored and a check will be forced
         """
 
-        if not sickbeard.VERSION_NOTIFY and not sickbeard.AUTO_UPDATE and not force:
+        if not self.updater or not sickbeard.VERSION_NOTIFY and not sickbeard.AUTO_UPDATE and not force:
             logger.log(u"Version checking is disabled, not checking for the newest version")
             return False
 
+        # checking for updates
         if not sickbeard.AUTO_UPDATE:
-            logger.log(u"Checking if " + self.install_type + " needs an update")
+            logger.log(u"Checking for updates using " + self.install_type.upper())
+
         if not self.updater.need_update():
             sickbeard.NEWEST_VERSION_STRING = None
+
             if not sickbeard.AUTO_UPDATE:
                 logger.log(u"No update needed")
 
-            if force:
-                ui.notifications.message('No update needed')
+                if force:
+                    ui.notifications.message('No update needed')
+
+            # no updates needed
             return False
 
+        # found updates
         self.updater.set_newest_text()
         return True
 
     def update(self):
-        # update branch with current config branch value
-        self.updater.branch = sickbeard.BRANCH
+        if self.updater:
+            # update branch with current config branch value
+            self.updater.branch = sickbeard.BRANCH
 
-        # check for updates
-        if self.updater.need_update():
-            return self.updater.update()
+            # check for updates
+            if self.updater.need_update():
+                return self.updater.update()
 
     def list_remote_branches(self):
-        return self.updater.list_remote_branches()
+        if self.updater:
+            return self.updater.list_remote_branches()
 
     def get_branch(self):
-        return self.updater.branch
+        if self.updater:
+            return self.updater.branch
 
 
 class UpdateManager():
@@ -136,159 +143,6 @@ class UpdateManager():
     def get_update_url(self):
         return sickbeard.WEB_ROOT + "/home/update/?pid=" + str(sickbeard.PID)
 
-
-class WindowsUpdateManager(UpdateManager):
-    def __init__(self):
-        self.github_org = self.get_github_org()
-        self.github_repo = self.get_github_repo()
-
-        self.branch = sickbeard.BRANCH
-        if sickbeard.BRANCH == '':
-            self.branch = self._find_installed_branch()
-
-        self._cur_version = None
-        self._cur_commit_hash = None
-        self._newest_version = None
-
-        self.gc_url = 'http://code.google.com/p/sickbeard/downloads/list'
-        self.version_url = 'https://raw.github.com/' + self.github_org + '/' + self.github_repo + '/' + self.branch + '/updates.txt'
-
-    def _find_installed_version(self):
-        version = ''
-
-        try:
-            version = sickbeard.BRANCH
-            return int(version[6:])
-        except ValueError:
-            logger.log(u"Unknown SickRage Windows binary release: " + version, logger.ERROR)
-            return None
-
-    def _find_installed_branch(self):
-        return 'windows_binaries'
-
-    def _find_newest_version(self, whole_link=False):
-        """
-        Checks git for the newest Windows binary build. Returns either the
-        build number or the entire build URL depending on whole_link's value.
-
-        whole_link: If True, returns the entire URL to the release. If False, it returns
-                    only the build number. default: False
-        """
-
-        regex = ".*SickRage\-win32\-alpha\-build(\d+)(?:\.\d+)?\.zip"
-
-        version_url_data = helpers.getURL(self.version_url)
-        if not version_url_data:
-            return
-
-        for curLine in version_url_data.splitlines():
-            logger.log(u"checking line " + curLine, logger.DEBUG)
-            match = re.match(regex, curLine)
-            if match:
-                logger.log(u"found a match", logger.DEBUG)
-                if whole_link:
-                    return curLine.strip()
-                else:
-                    return int(match.group(1))
-
-    def need_update(self):
-        if self.branch != self._find_installed_branch():
-            logger.log(u"Branch checkout: " + self._find_installed_branch() + "->" + self.branch, logger.DEBUG)
-            return True
-
-        self._cur_version = self._find_installed_version()
-        self._newest_version = self._find_newest_version()
-
-        logger.log(u"newest version: " + repr(self._newest_version), logger.DEBUG)
-        if self._newest_version and self._newest_version > self._cur_version:
-            return True
-
-        return False
-
-    def set_newest_text(self):
-
-        sickbeard.NEWEST_VERSION_STRING = None
-
-        if not self._cur_version:
-            newest_text = "Unknown SickRage Windows binary version. Not updating with original version."
-        else:
-            newest_text = 'There is a <a href="' + self.gc_url + '" onclick="window.open(this.href); return false;">newer version available</a> (build ' + str(
-                self._newest_version) + ')'
-            newest_text += "&mdash; <a href=\"" + self.get_update_url() + "\">Update Now</a>"
-
-        sickbeard.NEWEST_VERSION_STRING = newest_text
-
-    def update(self):
-
-        zip_download_url = self._find_newest_version(True)
-        logger.log(u"new_link: " + repr(zip_download_url), logger.DEBUG)
-
-        if not zip_download_url:
-            logger.log(u"Unable to find a new version link on google code, not updating")
-            return False
-
-        try:
-            # prepare the update dir
-            sr_update_dir = ek.ek(os.path.join, sickbeard.PROG_DIR, u'sr-update')
-
-            if os.path.isdir(sr_update_dir):
-                logger.log(u"Clearing out update folder " + sr_update_dir + " before extracting")
-                shutil.rmtree(sr_update_dir)
-
-            logger.log(u"Creating update folder " + sr_update_dir + " before extracting")
-            os.makedirs(sr_update_dir)
-
-            # retrieve file
-            logger.log(u"Downloading update from " + zip_download_url)
-            zip_download_path = os.path.join(sr_update_dir, u'sr-update.zip')
-            urllib.urlretrieve(zip_download_url, zip_download_path)
-
-            if not ek.ek(os.path.isfile, zip_download_path):
-                logger.log(u"Unable to retrieve new version from " + zip_download_url + ", can't update", logger.ERROR)
-                return False
-
-            if not ek.ek(zipfile.is_zipfile, zip_download_path):
-                logger.log(u"Retrieved version from " + zip_download_url + " is corrupt, can't update", logger.ERROR)
-                return False
-
-            # extract to sr-update dir
-            logger.log(u"Unzipping from " + str(zip_download_path) + " to " + sr_update_dir)
-            update_zip = zipfile.ZipFile(zip_download_path, 'r')
-            update_zip.extractall(sr_update_dir)
-            update_zip.close()
-
-            # delete the zip
-            logger.log(u"Deleting zip file from " + str(zip_download_path))
-            os.remove(zip_download_path)
-
-            # find update dir name
-            update_dir_contents = [x for x in os.listdir(sr_update_dir) if
-                                   os.path.isdir(os.path.join(sr_update_dir, x))]
-
-            if len(update_dir_contents) != 1:
-                logger.log(u"Invalid update data, update failed. Maybe try deleting your sr-update folder?",
-                           logger.ERROR)
-                return False
-
-            content_dir = os.path.join(sr_update_dir, update_dir_contents[0])
-            old_update_path = os.path.join(content_dir, u'updater.exe')
-            new_update_path = os.path.join(sickbeard.PROG_DIR, u'updater.exe')
-            logger.log(u"Copying new update.exe file from " + old_update_path + " to " + new_update_path)
-            shutil.move(old_update_path, new_update_path)
-
-            # Notify update successful
-            notifiers.notify_git_update(sickbeard.NEWEST_VERSION_STRING)
-
-        except Exception, e:
-            logger.log(u"Error while trying to update: " + ex(e), logger.ERROR)
-            return False
-
-        return True
-
-    def list_remote_branches(self):
-        return ['windows_binaries']
-
-
 class GitUpdateManager(UpdateManager):
     def __init__(self):
         self._git_path = self._find_working_git()
@@ -553,6 +407,23 @@ class GitUpdateManager(UpdateManager):
             if sickbeard.NOTIFY_ON_UPDATE:
                 notifiers.notify_git_update(sickbeard.CUR_COMMIT_HASH if sickbeard.CUR_COMMIT_HASH else "")
             return True
+        else:
+            # perform a hard reset to try and resolve the issue
+            if self.reset() and self.update():
+                return True
+
+        return False
+
+    def reset(self):
+        """
+        Calls git reset --hard to perform a hard reset. Returns a bool depending
+        on the call's success.
+        """
+        if sickbeard.GIT_RESET:
+            output, err, exit_status = self._run_git(self._git_path, 'reset --hard')  # @UnusedVariable
+
+            if exit_status == 0:
+                return True
 
         return False
 
diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py
index f5f41d6ed422f63bae91787113482f014d88af8f..f0025b2b33fd84f925b152caee75385956255d2b 100644
--- a/sickbeard/webapi.py
+++ b/sickbeard/webapi.py
@@ -25,8 +25,8 @@ import urllib
 import datetime
 import re
 import traceback
+
 import sickbeard
-import webserve
 
 from sickbeard import db, logger, exceptions, history, ui, helpers
 from sickbeard import encodingKludge as ek
@@ -36,8 +36,8 @@ from sickbeard import classes
 from sickbeard import processTV
 from sickbeard import network_timezones, sbdatetime
 from sickbeard.exceptions import ex
-from sickbeard.common import SNATCHED, SNATCHED_PROPER, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED, UNKNOWN
-from common import Quality, Overview, qualityPresetStrings, statusStrings
+from sickbeard.common import Quality, Overview, qualityPresetStrings, statusStrings, SNATCHED, SNATCHED_PROPER, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED, UNKNOWN
+from sickbeard.webserve import WebRoot
 
 try:
     import json
@@ -46,6 +46,8 @@ except ImportError:
 
 from lib import subliminal
 
+from tornado.web import RequestHandler
+
 indexer_ids = ["indexerid", "tvdbid", "tvrageid"]
 
 dateFormat = "%Y-%m-%d"
@@ -67,14 +69,21 @@ result_type_map = {RESULT_SUCCESS: "success",
 }
 # basically everything except RESULT_SUCCESS / success is bad
 
-class Api(webserve.MainHandler):
+class ApiHandler(RequestHandler):
     """ api class that returns json results """
-    version = 4  # use an int since float-point is unpredictible
+    version = 5  # use an int since float-point is unpredictible
     intent = 4
 
-    def index(self, *args, **kwargs):
-        self.apiKey = sickbeard.API_KEY
-        access, accessMsg, args, kwargs = self._grand_access(self.apiKey, args, kwargs)
+    def __init__(self, *args, **kwargs):
+        super(ApiHandler, self).__init__(*args, **kwargs)
+
+    def get(self, *args, **kwargs):
+        kwargs = self.request.arguments
+        for arg, value in kwargs.items():
+            if len(value) == 1:
+                kwargs[arg] = value[0]
+
+        args = args[1:]
 
         # set the output callback
         # default json
@@ -82,15 +91,16 @@ class Api(webserve.MainHandler):
                               'image': lambda x: x['image'],
         }
 
-        # do we have acces ?
-        if access:
-            logger.log(accessMsg, logger.DEBUG)
-        else:
+        if sickbeard.USE_API is not True:
+            accessMsg = u"API :: " + self.request.remote_ip + " - SB API Disabled. ACCESS DENIED"
             logger.log(accessMsg, logger.WARNING)
-            return outputCallbackDict['default'](_responds(RESULT_DENIED, msg=accessMsg))
+            return self.finish(outputCallbackDict['default'](_responds(RESULT_DENIED, msg=accessMsg)))
+        else:
+            accessMsg = u"API :: " + self.request.remote_ip + " - gave correct API KEY. ACCESS GRANTED"
+            logger.log(accessMsg, logger.DEBUG)
 
         # set the original call_dispatcher as the local _call_dispatcher
-        _call_dispatcher = call_dispatcher
+        _call_dispatcher = self.call_dispatcher
         # if profile was set wrap "_call_dispatcher" in the profile function
         if 'profile' in kwargs:
             from lib.profilehooks import profile
@@ -100,12 +110,11 @@ class Api(webserve.MainHandler):
 
         # if debug was set call the "_call_dispatcher"
         if 'debug' in kwargs:
-            outDict = _call_dispatcher(self, args,
-                                       kwargs)  # this way we can debug the cherry.py traceback in the browser
+            outDict = _call_dispatcher(args, kwargs)  # this way we can debug the cherry.py traceback in the browser
             del kwargs["debug"]
         else:  # if debug was not set we wrap the "call_dispatcher" in a try block to assure a json output
             try:
-                outDict = _call_dispatcher(self, args, kwargs)
+                outDict = _call_dispatcher(args, kwargs)
             except Exception, e:  # real internal error oohhh nooo :(
                 logger.log(u"API :: " + ex(e), logger.ERROR)
                 errorData = {"error_msg": ex(e),
@@ -119,47 +128,7 @@ class Api(webserve.MainHandler):
         else:
             outputCallback = outputCallbackDict['default']
 
-        return outputCallback(outDict)
-
-    def builder(self):
-        """ expose the api-builder template """
-        t = webserve.PageTemplate(headers=self.request.headers, file="apiBuilder.tmpl")
-
-        def titler(x):
-            if not x or sickbeard.SORT_ARTICLE:
-                return x
-            if x.lower().startswith('a '):
-                x = x[2:]
-            elif x.lower().startswith('an '):
-                x = x[3:]
-            elif x.lower().startswith('the '):
-                x = x[4:]
-            return x
-
-        t.sortedShowList = sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))
-
-        seasonSQLResults = {}
-        episodeSQLResults = {}
-
-        myDB = db.DBConnection(row_type="dict")
-        for curShow in t.sortedShowList:
-            seasonSQLResults[curShow.indexerid] = myDB.select(
-                "SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC", [curShow.indexerid])
-
-        for curShow in t.sortedShowList:
-            episodeSQLResults[curShow.indexerid] = myDB.select(
-                "SELECT DISTINCT season,episode FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC",
-                [curShow.indexerid])
-
-        t.seasonSQLResults = seasonSQLResults
-        t.episodeSQLResults = episodeSQLResults
-
-        if len(sickbeard.API_KEY) == 32:
-            t.apikey = sickbeard.API_KEY
-        else:
-            t.apikey = "api key not generated"
-
-        return webserve._munge(t)
+        return self.finish(outputCallback(outDict))
 
     def _out_as_json(self, dict):
         self.set_header("Content-Type", "application/json;charset=UTF-8'")
@@ -174,147 +143,119 @@ class Api(webserve.MainHandler):
                 e) + '"}'
         return out
 
-    def _grand_access(self, realKey, args, kwargs):
-        """ validate api key and log result """
-        remoteIp = self.request.remote_ip
-        apiKey = kwargs.get("apikey", None)
-        if not apiKey:
-            if args:  # if we have keyless vars we assume first one is the api key, always !
-                apiKey = args[0]
-                args = args[1:]  # remove the apikey from the args tuple
-        else:
-            del kwargs["apikey"]
-
-        if not sickbeard.USE_API:
-            msg = u"API :: " + remoteIp + " - SR API Disabled. ACCESS DENIED"
-            return False, msg, args, kwargs
-        elif apiKey == realKey:
-            msg = u"API :: " + remoteIp + " - gave correct API KEY. ACCESS GRANTED"
-            return True, msg, args, kwargs
-        elif not apiKey:
-            msg = u"API :: " + remoteIp + " - gave NO API KEY. ACCESS DENIED"
-            return False, msg, args, kwargs
-        else:
-            msg = u"API :: " + remoteIp + " - gave WRONG API KEY " + apiKey + ". ACCESS DENIED"
-            return False, msg, args, kwargs
-
 
-def call_dispatcher(handler, args, kwargs):
-    """ calls the appropriate CMD class
-        looks for a cmd in args and kwargs
-        or calls the TVDBShorthandWrapper when the first args element is a number
-        or returns an error that there is no such cmd
-    """
-    logger.log(u"API :: all args: '" + str(args) + "'", logger.DEBUG)
-    logger.log(u"API :: all kwargs: '" + str(kwargs) + "'", logger.DEBUG)
-    # logger.log(u"API :: dateFormat: '" + str(dateFormat) + "'", logger.DEBUG)
+    def call_dispatcher(self, args, kwargs):
+        """ calls the appropriate CMD class
+            looks for a cmd in args and kwargs
+            or calls the TVDBShorthandWrapper when the first args element is a number
+            or returns an error that there is no such cmd
+        """
+        logger.log(u"API :: all args: '" + str(args) + "'", logger.DEBUG)
+        logger.log(u"API :: all kwargs: '" + str(kwargs) + "'", logger.DEBUG)
 
-    cmds = None
-    if args:
-        cmds = args[0]
-        args = args[1:]
+        cmds = None
+        if args:
+            cmds = args[0]
+            args = args[1:]
 
-    if "cmd" in kwargs:
-        cmds = kwargs["cmd"]
-        del kwargs["cmd"]
-
-    outDict = {}
-    if cmds != None:
-        cmds = cmds.split("|")
-        multiCmds = bool(len(cmds) > 1)
-        for cmd in cmds:
-            curArgs, curKwargs = filter_params(cmd, args, kwargs)
-            cmdIndex = None
-            if len(cmd.split("_")) > 1:  # was a index used for this cmd ?
-                cmd, cmdIndex = cmd.split("_")  # this gives us the clear cmd and the index
-
-            logger.log(u"API :: " + cmd + ": curKwargs " + str(curKwargs), logger.DEBUG)
-            if not (multiCmds and cmd in ('show.getposter', 'show.getbanner')):  # skip these cmd while chaining
-                try:
-                    if cmd in _functionMaper:
-                        curOutDict = _functionMaper.get(cmd)(handler, curArgs,
-                                                             curKwargs).run()  # get the cmd class, init it and run()
-                    elif _is_int(cmd):
-                        curOutDict = TVDBShorthandWrapper(handler, curArgs, curKwargs, cmd).run()
+        if "cmd" in kwargs:
+            cmds = kwargs["cmd"]
+            del kwargs["cmd"]
+
+        outDict = {}
+        if cmds is not None:
+            cmds = cmds.split("|")
+            multiCmds = bool(len(cmds) > 1)
+            for cmd in cmds:
+                curArgs, curKwargs = self.filter_params(cmd, args, kwargs)
+                cmdIndex = None
+                if len(cmd.split("_")) > 1:  # was a index used for this cmd ?
+                    cmd, cmdIndex = cmd.split("_")  # this gives us the clear cmd and the index
+
+                logger.log(u"API :: " + cmd + ": curKwargs " + str(curKwargs), logger.DEBUG)
+                if not (multiCmds and cmd in ('show.getposter', 'show.getbanner')):  # skip these cmd while chaining
+                    try:
+                        if cmd in _functionMaper:
+                            curOutDict = _functionMaper.get(cmd)(curArgs, curKwargs).run()
+                        elif _is_int(cmd):
+                            curOutDict = TVDBShorthandWrapper(curArgs, curKwargs, cmd).run()
+                        else:
+                            curOutDict = _responds(RESULT_ERROR, "No such cmd: '" + cmd + "'")
+                    except ApiError, e:  # Api errors that we raised, they are harmless
+                        curOutDict = _responds(RESULT_ERROR, msg=ex(e))
+                else:  # if someone chained one of the forbiden cmds they will get an error for this one cmd
+                    curOutDict = _responds(RESULT_ERROR, msg="The cmd '" + cmd + "' is not supported while chaining")
+
+                if multiCmds:
+                    # note: if multiple same cmds are issued but one has not an index defined it will override all others
+                    # or the other way around, this depends on the order of the cmds
+                    # this is not a bug
+                    if cmdIndex is None:  # do we need a index dict for this cmd ?
+                        outDict[cmd] = curOutDict
                     else:
-                        curOutDict = _responds(RESULT_ERROR, "No such cmd: '" + cmd + "'")
-                except ApiError, e:  # Api errors that we raised, they are harmless
-                    curOutDict = _responds(RESULT_ERROR, msg=ex(e))
-            else:  # if someone chained one of the forbiden cmds they will get an error for this one cmd
-                curOutDict = _responds(RESULT_ERROR, msg="The cmd '" + cmd + "' is not supported while chaining")
-
-            if multiCmds:
-                # note: if multiple same cmds are issued but one has not an index defined it will override all others
-                # or the other way around, this depends on the order of the cmds
-                # this is not a bug
-                if cmdIndex is None:  # do we need a index dict for this cmd ?
-                    outDict[cmd] = curOutDict
+                        if not cmd in outDict:
+                            outDict[cmd] = {}
+                        outDict[cmd][cmdIndex] = curOutDict
                 else:
-                    if not cmd in outDict:
-                        outDict[cmd] = {}
-                    outDict[cmd][cmdIndex] = curOutDict
-            else:
-                outDict = curOutDict
+                    outDict = curOutDict
 
-        if multiCmds:  # if we had multiple cmds we have to wrap it in a response dict
-            outDict = _responds(RESULT_SUCCESS, outDict)
-    else:  # index / no cmd given
-        outDict = CMD_SickBeard(handler, args, kwargs).run()
+            if multiCmds:  # if we had multiple cmds we have to wrap it in a response dict
+                outDict = _responds(RESULT_SUCCESS, outDict)
+        else:  # index / no cmd given
+            outDict = CMD_SickBeard(args, kwargs).run()
 
-    return outDict
+        return outDict
 
 
-def filter_params(cmd, args, kwargs):
-    """ return only params kwargs that are for cmd
-        and rename them to a clean version (remove "<cmd>_")
-        args are shared across all cmds
+    def filter_params(self, cmd, args, kwargs):
+        """ return only params kwargs that are for cmd
+            and rename them to a clean version (remove "<cmd>_")
+            args are shared across all cmds
 
-        all args and kwarks are lowerd
+            all args and kwarks are lowerd
 
-        cmd are separated by "|" e.g. &cmd=shows|future
-        kwargs are namespaced with "." e.g. show.indexerid=101501
-        if a karg has no namespace asing it anyways (global)
+            cmd are separated by "|" e.g. &cmd=shows|future
+            kwargs are namespaced with "." e.g. show.indexerid=101501
+            if a karg has no namespace asing it anyways (global)
 
-        full e.g.
-        /api?apikey=1234&cmd=show.seasonlist_asd|show.seasonlist_2&show.seasonlist_asd.indexerid=101501&show.seasonlist_2.indexerid=79488&sort=asc
+            full e.g.
+            /api?apikey=1234&cmd=show.seasonlist_asd|show.seasonlist_2&show.seasonlist_asd.indexerid=101501&show.seasonlist_2.indexerid=79488&sort=asc
 
-        two calls of show.seasonlist
-        one has the index "asd" the other one "2"
-        the "indexerid" kwargs / params have the indexed cmd as a namspace
-        and the kwarg / param "sort" is a used as a global
-    """
-    curArgs = []
-    for arg in args:
-        curArgs.append(arg.lower())
-    curArgs = tuple(curArgs)
-
-    curKwargs = {}
-    for kwarg in kwargs:
-        if kwarg.find(cmd + ".") == 0:
-            cleanKey = kwarg.rpartition(".")[2]
-            curKwargs[cleanKey] = kwargs[kwarg].lower()
-        elif not "." in kwarg:  # the kwarg was not namespaced therefore a "global"
-            curKwargs[kwarg] = kwargs[kwarg]
-    return curArgs, curKwargs
-
-
-class ApiCall(object):
+            two calls of show.seasonlist
+            one has the index "asd" the other one "2"
+            the "indexerid" kwargs / params have the indexed cmd as a namspace
+            and the kwarg / param "sort" is a used as a global
+        """
+        curArgs = []
+        for arg in args:
+            curArgs.append(arg.lower())
+        curArgs = tuple(curArgs)
+
+        curKwargs = {}
+        for kwarg in kwargs:
+            if kwarg.find(cmd + ".") == 0:
+                cleanKey = kwarg.rpartition(".")[2]
+                curKwargs[cleanKey] = kwargs[kwarg].lower()
+            elif not "." in kwarg:  # the kwarg was not namespaced therefore a "global"
+                curKwargs[kwarg] = kwargs[kwarg]
+        return curArgs, curKwargs
+
+
+class ApiCall(ApiHandler):
     _help = {"desc": "No help message available. Please tell the devs that a help msg is missing for this cmd"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # missing
         try:
             if self._missing:
                 self.run = self.return_missing
         except AttributeError:
             pass
+
         # help
         if 'help' in kwargs:
             self.run = self.return_help
 
-        # RequestHandler
-        self.handler = handler
-
     def run(self):
         # override with real output function in subclass
         return {}
@@ -394,12 +335,12 @@ class ApiCall(object):
         if required:
             try:
                 self._missing
-                self._requiredParams.append(key)
+                self._requiredParams += [key]
             except AttributeError:
                 self._missing = []
-                self._requiredParams = {}
-                self._requiredParams[key] = {"allowedValues": allowedValues,
-                                             "defaultValue": orgDefault}
+                self._requiredParams = {key: {"allowedValues": allowedValues,
+                                              "defaultValue": orgDefault}}
+
             if missing and key not in self._missing:
                 self._missing.append(key)
         else:
@@ -487,8 +428,7 @@ class ApiCall(object):
 class TVDBShorthandWrapper(ApiCall):
     _help = {"desc": "this is an internal function wrapper. call the help command directly for more information"}
 
-    def __init__(self, handler, args, kwargs, sid):
-        self.handler = handler
+    def __init__(self, args, kwargs, sid):
         self.origArgs = args
         self.kwargs = kwargs
         self.sid = sid
@@ -497,17 +437,17 @@ class TVDBShorthandWrapper(ApiCall):
         self.e, args = self.check_params(args, kwargs, "e", None, False, "ignore", [])
         self.args = args
 
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ internal function wrapper """
         args = (self.sid,) + self.origArgs
         if self.e:
-            return CMD_Episode(self.handler, args, self.kwargs).run()
+            return CMD_Episode(args, self.kwargs).run()
         elif self.s:
-            return CMD_ShowSeasons(self.handler, args, self.kwargs).run()
+            return CMD_ShowSeasons(args, self.kwargs).run()
         else:
-            return CMD_Show(self.handler, args, self.kwargs).run()
+            return CMD_Show(args, self.kwargs).run()
 
 
 # ###############################
@@ -692,12 +632,12 @@ class CMD_Help(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.subject, args = self.check_params(args, kwargs, "subject", "help", False, "string",
                                                _functionMaper.keys())
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display help information for a given subject/command """
@@ -717,7 +657,7 @@ class CMD_ComingEpisodes(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.sort, args = self.check_params(args, kwargs, "sort", "date", False, "string",
@@ -729,7 +669,7 @@ class CMD_ComingEpisodes(ApiCall):
                                               False, "int",
                                               [0, 1])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display the coming episodes """
@@ -842,7 +782,7 @@ class CMD_Episode(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
@@ -850,7 +790,7 @@ class CMD_Episode(ApiCall):
         # optional
         self.fullPath, args = self.check_params(args, kwargs, "full_path", 0, False, "bool", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display detailed info about an episode """
@@ -906,14 +846,14 @@ class CMD_EpisodeSearch(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
         self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ search for an episode """
@@ -960,7 +900,7 @@ class CMD_EpisodeSetStatus(ApiCall):
 
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
@@ -970,7 +910,7 @@ class CMD_EpisodeSetStatus(ApiCall):
         self.e, args = self.check_params(args, kwargs, "episode", None, False, "int", [])
         self.force, args = self.check_params(args, kwargs, "force", 0, False, "bool", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ set status of an episode or a season (when no ep is provided) """
@@ -1072,14 +1012,14 @@ class CMD_SubtitleSearch(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         self.s, args = self.check_params(args, kwargs, "season", None, True, "int", [])
         self.e, args = self.check_params(args, kwargs, "episode", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ search episode subtitles """
@@ -1125,13 +1065,13 @@ class CMD_Exceptions(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
 
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display scene exceptions for all or a given show """
@@ -1168,14 +1108,14 @@ class CMD_History(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.limit, args = self.check_params(args, kwargs, "limit", 100, False, "int", [])
         self.type, args = self.check_params(args, kwargs, "type", None, False, "string",
                                             ["downloaded", "snatched"])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display sickrage downloaded/snatched history """
@@ -1226,11 +1166,11 @@ class CMD_HistoryClear(ApiCall):
     _help = {"desc": "clear sickrage's history",
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ clear sickrage's history """
@@ -1244,11 +1184,11 @@ class CMD_HistoryTrim(ApiCall):
     _help = {"desc": "trim sickrage's history by removing entries greater than 30 days old"
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ trim sickrage's history """
@@ -1264,12 +1204,12 @@ class CMD_Failed(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.limit, args = self.check_params(args, kwargs, "limit", 100, False, "int", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display failed downloads """
@@ -1287,11 +1227,11 @@ class CMD_Failed(ApiCall):
 class CMD_Backlog(ApiCall):
     _help = {"desc": "display backlogged episodes"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display backlogged episodes """
@@ -1309,10 +1249,10 @@ class CMD_Backlog(ApiCall):
 
             for curResult in sqlResults:
 
-                curEpCat = curShow.getOverview(int(curResult["status"]))
+                curEpCat = curShow.getOverview(int(curResult["status"] or -1))
                 if curEpCat and curEpCat in (Overview.WANTED, Overview.QUAL):
                     showEps.append(curResult)
-            
+
             if showEps:
                 shows.append({
                     "indexerid": curShow.indexerid,
@@ -1320,7 +1260,7 @@ class CMD_Backlog(ApiCall):
                     "status": curShow.status,
                     "episodes": showEps
                 })
-               
+
         return _responds(RESULT_SUCCESS, shows)
 
 class CMD_Logs(ApiCall):
@@ -1329,13 +1269,13 @@ class CMD_Logs(ApiCall):
                  "desc": "the minimum level classification of log entries to show, with each level inherting its above level"}}
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.min_level, args = self.check_params(args, kwargs, "min_level", "error", False, "string",
                                                  ["error", "warning", "info", "debug"])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ view sickrage's log """
@@ -1343,8 +1283,8 @@ class CMD_Logs(ApiCall):
         minLevel = logger.reverseNames[str(self.min_level).upper()]
 
         data = []
-        if os.path.isfile(logger.sb_log_instance.log_file_path):
-            with ek.ek(open, logger.sb_log_instance.log_file_path) as f:
+        if os.path.isfile(logger.logFile):
+            with ek.ek(open, logger.logFile) as f:
                 data = f.readlines()
 
         regex = "^(\d\d\d\d)\-(\d\d)\-(\d\d)\s*(\d\d)\:(\d\d):(\d\d)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$"
@@ -1395,7 +1335,7 @@ class CMD_PostProcess(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.path, args = self.check_params(args, kwargs, "path", None, False, "string", [])
@@ -1407,7 +1347,7 @@ class CMD_PostProcess(ApiCall):
         self.type, args = self.check_params(args, kwargs, "type", "auto", None, "string",
                                             ["auto", "manual"])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ Starts the postprocess """
@@ -1432,15 +1372,15 @@ class CMD_PostProcess(ApiCall):
 class CMD_SickBeard(ApiCall):
     _help = {"desc": "display misc sickrage related information"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display misc sickrage related information """
-        data = {"sb_version": sickbeard.BRANCH, "api_version": Api.version,
+        data = {"sr_version": sickbeard.BRANCH, "api_version": self.version,
                 "api_commands": sorted(_functionMaper.keys())}
         return _responds(RESULT_SUCCESS, data)
 
@@ -1453,13 +1393,13 @@ class CMD_SickBeardAddRootDir(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.location, args = self.check_params(args, kwargs, "location", None, True, "string", [])
         # optional
         self.default, args = self.check_params(args, kwargs, "default", 0, False, "bool", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ add a parent directory to sickrage's config """
@@ -1506,11 +1446,11 @@ class CMD_SickBeardAddRootDir(ApiCall):
 class CMD_SickBeardCheckScheduler(ApiCall):
     _help = {"desc": "query the scheduler"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ query the scheduler """
@@ -1532,12 +1472,12 @@ class CMD_SickBeardDeleteRootDir(ApiCall):
              "requiredParameters": {"location": {"desc": "the full path to root (parent) directory"}}
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.location, args = self.check_params(args, kwargs, "location", None, True, "string", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ delete a parent directory from sickrage's config """
@@ -1576,11 +1516,11 @@ class CMD_SickBeardDeleteRootDir(ApiCall):
 class CMD_SickBeardGetDefaults(ApiCall):
     _help = {"desc": "get sickrage user defaults"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ get sickrage user defaults """
@@ -1596,15 +1536,15 @@ class CMD_SickBeardGetDefaults(ApiCall):
 class CMD_SickBeardGetMessages(ApiCall):
     _help = {"desc": "get all messages"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         messages = []
-        for cur_notification in ui.notifications.get_notifications(self.handler.request.remote_ip):
+        for cur_notification in ui.notifications.get_notifications(self.request.remote_ip):
             messages.append({"title": cur_notification.title,
                              "message": cur_notification.message,
                              "type": cur_notification.type})
@@ -1614,11 +1554,11 @@ class CMD_SickBeardGetMessages(ApiCall):
 class CMD_SickBeardGetRootDirs(ApiCall):
     _help = {"desc": "get sickrage user parent directories"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ get the parent directories defined in sickrage's config """
@@ -1631,12 +1571,12 @@ class CMD_SickBeardPauseBacklog(ApiCall):
              "optionalParameters": {"pause ": {"desc": "pause or unpause the global backlog"}}
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.pause, args = self.check_params(args, kwargs, "pause", 0, False, "bool", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ pause the backlog search """
@@ -1651,15 +1591,15 @@ class CMD_SickBeardPauseBacklog(ApiCall):
 class CMD_SickBeardPing(ApiCall):
     _help = {"desc": "check to see if sickrage is running"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ check to see if sickrage is running """
-        self.handler.set_header('Cache-Control', "max-age=0,no-cache,no-store")
+        self.set_header('Cache-Control', "max-age=0,no-cache,no-store")
         if sickbeard.started:
             return _responds(RESULT_SUCCESS, {"pid": sickbeard.PID}, "Pong")
         else:
@@ -1669,11 +1609,11 @@ class CMD_SickBeardPing(ApiCall):
 class CMD_SickBeardRestart(ApiCall):
     _help = {"desc": "restart sickrage"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ restart sickrage """
@@ -1697,7 +1637,7 @@ class CMD_SickBeardSearchIndexers(ApiCall):
         'de': 14, 'da': 10, 'fi': 11, 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32,
         'sv': 8, 'sl': 30}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.name, args = self.check_params(args, kwargs, "name", None, False, "string", [])
@@ -1706,7 +1646,7 @@ class CMD_SickBeardSearchIndexers(ApiCall):
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, False, "int", [])
 
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ search for show at tvdb with a given string and language """
@@ -1784,9 +1724,9 @@ class CMD_SickBeardSearchTVDB(CMD_SickBeardSearchIndexers):
     }
 
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         self.indexerid, args = self.check_params(args, kwargs, "tvdbid", None, False, "int", [])
-        CMD_SickBeardSearchIndexers.__init__(self, handler, args, kwargs)
+        CMD_SickBeardSearchIndexers.__init__(self, args, kwargs)
 
 
 class CMD_SickBeardSearchTVRAGE(CMD_SickBeardSearchIndexers):
@@ -1797,9 +1737,9 @@ class CMD_SickBeardSearchTVRAGE(CMD_SickBeardSearchIndexers):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         self.indexerid, args = self.check_params(args, kwargs, "tvrageid", None, False, "int", [])
-        CMD_SickBeardSearchIndexers.__init__(self, handler, args, kwargs)
+        CMD_SickBeardSearchIndexers.__init__(self, args, kwargs)
 
 
 class CMD_SickBeardSetDefaults(ApiCall):
@@ -1811,7 +1751,7 @@ class CMD_SickBeardSetDefaults(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list",
@@ -1828,7 +1768,7 @@ class CMD_SickBeardSetDefaults(ApiCall):
         self.status, args = self.check_params(args, kwargs, "status", None, False, "string",
                                               ["wanted", "skipped", "archived", "ignored"])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ set sickrage user defaults """
@@ -1883,11 +1823,11 @@ class CMD_SickBeardSetDefaults(ApiCall):
 class CMD_SickBeardShutdown(ApiCall):
     _help = {"desc": "shutdown sickrage"}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ shutdown sickrage """
@@ -1907,12 +1847,12 @@ class CMD_Show(ApiCall):
 
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display information for a given show """
@@ -1921,8 +1861,8 @@ class CMD_Show(ApiCall):
             return _responds(RESULT_FAILURE, msg="Show not found")
 
         showDict = {}
-        showDict["season_list"] = CMD_ShowSeasonList(self.handler, (), {"indexerid": self.indexerid}).run()["data"]
-        showDict["cache"] = CMD_ShowCache(self.handler, (), {"indexerid": self.indexerid}).run()["data"]
+        showDict["season_list"] = CMD_ShowSeasonList((), {"indexerid": self.indexerid}).run()["data"]
+        showDict["cache"] = CMD_ShowCache((), {"indexerid": self.indexerid}).run()["data"]
 
         genreList = []
         if showObj.genre:
@@ -1951,6 +1891,20 @@ class CMD_Show(ApiCall):
         showDict["sports"] = showObj.sports
         showDict["anime"] = showObj.anime
         showDict["airs"] = str(showObj.airs).replace('am', ' AM').replace('pm', ' PM').replace('  ', ' ')
+        showDict["dvdorder"] = showObj.dvdorder
+
+        if showObj.rls_require_words:
+            showDict["rls_require_words"] = showObj.rls_require_words.split(", ")
+        else:
+            showDict["rls_require_words"] = []
+
+        if showObj.rls_ignore_words:
+            showDict["rls_ignore_words"] = showObj.rls_ignore_words.split(", ")
+        else:
+            showDict["rls_ignore_words"] = []
+
+        showDict["scene"] = showObj.scene
+        showDict["archive_firstmatch"] = showObj.archive_firstmatch
 
         showDict["indexerid"] = showObj.indexerid
         showDict["tvdbid"] = helpers.mapIndexersToShow(showObj)[1]
@@ -1989,7 +1943,7 @@ class CMD_ShowAddExisting(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "", [])
 
@@ -2008,7 +1962,7 @@ class CMD_ShowAddExisting(ApiCall):
         self.subtitles, args = self.check_params(args, kwargs, "subtitles", int(sickbeard.USE_SUBTITLES),
                                                  False, "int", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ add a show in sickrage with an existing folder """
@@ -2020,8 +1974,7 @@ class CMD_ShowAddExisting(ApiCall):
             return _responds(RESULT_FAILURE, msg='Not a valid location')
 
         indexerName = None
-        indexerResult = CMD_SickBeardSearchIndexers(self.handler, [],
-                                                    {indexer_ids[self.indexer]: self.indexerid}).run()
+        indexerResult = CMD_SickBeardSearchIndexers([], {indexer_ids[self.indexer]: self.indexerid}).run()
 
         if indexerResult['result'] == result_type_map[RESULT_SUCCESS]:
             if not indexerResult['data']['results']:
@@ -2092,7 +2045,7 @@ class CMD_ShowAddNew(ApiCall):
         'de': 14, 'da': 10, 'fi': 11, 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32,
         'sv': 8, 'sl': 30}
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
 
@@ -2123,7 +2076,7 @@ class CMD_ShowAddNew(ApiCall):
             [])
 
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ add a show in sickrage with an existing folder """
@@ -2186,8 +2139,7 @@ class CMD_ShowAddNew(ApiCall):
             newStatus = self.status
 
         indexerName = None
-        indexerResult = CMD_SickBeardSearchIndexers(self.handler, [],
-                                                    {indexer_ids[self.indexer]: self.indexerid}).run()
+        indexerResult = CMD_SickBeardSearchIndexers([], {indexer_ids[self.indexer]: self.indexerid}).run()
 
         if indexerResult['result'] == result_type_map[RESULT_SUCCESS]:
             if not indexerResult['data']['results']:
@@ -2235,12 +2187,12 @@ class CMD_ShowCache(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ check sickrage's cache to see if the banner or poster image for a show is valid """
@@ -2272,15 +2224,17 @@ class CMD_ShowDelete(ApiCall):
              "optionalParameters": {
                  "tvdbid": {"desc": "thetvdb.com unique id of a show"},
                  "tvrageid": {"desc": "tvrage.com unique id of a show"},
+                 "removefiles":{"desc": "Deletes the files, there is no going back!"},
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
+        self.removefiles, args = self.check_params(args, kwargs, "removefiles", 0, False, "int", [0,1])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ delete a show in sickrage """
@@ -2292,7 +2246,12 @@ class CMD_ShowDelete(ApiCall):
                 showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):  # @UndefinedVariable
             return _responds(RESULT_FAILURE, msg="Show can not be deleted while being added or updated")
 
-        showObj.deleteShow()
+        if sickbeard.USE_TRAKT and sickbeard.TRAKT_SYNC:
+            # remove show from trakt.tv library
+            sickbeard.traktCheckerScheduler.action.removeShowFromTraktLibrary(showObj)
+
+        showObj.deleteShow(bool(self.removefiles))
+
         return _responds(RESULT_SUCCESS, msg=str(showObj.name) + " has been deleted")
 
 
@@ -2307,12 +2266,12 @@ class CMD_ShowGetQuality(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ get quality setting for a show in sickrage """
@@ -2336,16 +2295,16 @@ class CMD_ShowGetPoster(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ get the poster for a show in sickrage """
-        return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'poster')}
+        return {'outputType': 'image', 'image': WebRoot().showPoster(self.indexerid, 'poster')}
 
 
 class CMD_ShowGetBanner(ApiCall):
@@ -2359,16 +2318,16 @@ class CMD_ShowGetBanner(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ get the banner for a show in sickrage """
-        return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'banner')}
+        return {'outputType': 'image', 'image': WebRoot().showPoster(self.indexerid, 'banner')}
 
 
 class CMD_ShowPause(ApiCall):
@@ -2383,13 +2342,13 @@ class CMD_ShowPause(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         self.pause, args = self.check_params(args, kwargs, "pause", 0, False, "bool", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ set a show's paused state in sickrage """
@@ -2404,9 +2363,6 @@ class CMD_ShowPause(ApiCall):
             showObj.paused = 0
             return _responds(RESULT_SUCCESS, msg=str(showObj.name) + " has been unpaused")
 
-        return _responds(RESULT_FAILURE, msg=str(showObj.name) + " was unable to be paused")
-
-
 class CMD_ShowRefresh(ApiCall):
     _help = {"desc": "refresh a show in sickrage",
              "requiredParameters": {
@@ -2418,12 +2374,12 @@ class CMD_ShowRefresh(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ refresh a show in sickrage """
@@ -2451,7 +2407,7 @@ class CMD_ShowSeasonList(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
@@ -2459,7 +2415,7 @@ class CMD_ShowSeasonList(ApiCall):
                                             ["asc",
                                              "desc"])  # "asc" and "desc" default and fallback is "desc"
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display the season list for a given show """
@@ -2493,13 +2449,13 @@ class CMD_ShowSeasons(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         self.season, args = self.check_params(args, kwargs, "season", None, False, "int", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display a listing of episodes for all or a given show """
@@ -2566,7 +2522,7 @@ class CMD_ShowSetQuality(ApiCall):
         }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
@@ -2580,7 +2536,7 @@ class CMD_ShowSetQuality(ApiCall):
                                                 "fullhdwebdl",
                                                 "hdbluray", "fullhdbluray"])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ set the quality for a show in sickrage by taking in a deliminated
@@ -2632,12 +2588,12 @@ class CMD_ShowStats(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display episode statistics for a given show """
@@ -2741,12 +2697,12 @@ class CMD_ShowUpdate(ApiCall):
              }
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         self.indexerid, args = self.check_params(args, kwargs, "indexerid", None, True, "int", [])
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ update a show in sickrage """
@@ -2770,13 +2726,13 @@ class CMD_Shows(ApiCall):
              },
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         self.sort, args = self.check_params(args, kwargs, "sort", "id", False, "string", ["id", "name"])
         self.paused, args = self.check_params(args, kwargs, "paused", None, False, "bool", [])
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display_is_int_multi( self.indexerid )shows in sickrage """
@@ -2813,7 +2769,7 @@ class CMD_Shows(ApiCall):
                 showDict['next_ep_airdate'] = ''
 
             showDict["cache"] = \
-                CMD_ShowCache(self.handler, (), {"indexerid or tvdbid or tvrageid": curShow.indexerid}).run()["data"]
+                CMD_ShowCache((), {"indexerid": curShow.indexerid}).run()["data"]
             if not showDict["network"]:
                 showDict["network"] = ""
             if self.sort == "name":
@@ -2828,11 +2784,11 @@ class CMD_ShowsStats(ApiCall):
     _help = {"desc": "display the global shows and episode stats"
     }
 
-    def __init__(self, handler, args, kwargs):
+    def __init__(self, args, kwargs):
         # required
         # optional
         # super, missing, help
-        ApiCall.__init__(self, handler, args, kwargs)
+        ApiCall.__init__(self, args, kwargs)
 
     def run(self):
         """ display the global shows and episode stats """
@@ -2842,11 +2798,15 @@ class CMD_ShowsStats(ApiCall):
         today = str(datetime.date.today().toordinal())
         stats["shows_total"] = len(sickbeard.showList)
         stats["shows_active"] = len(
-            [show for show in sickbeard.showList if show.paused == 0 and show.status != "Ended"])
+            [show for show in sickbeard.showList if show.paused == 0 and "Unknown" not in show.status and "Ended" not in show.status])
         stats["ep_downloaded"] = myDB.select("SELECT COUNT(*) FROM tv_episodes WHERE status IN (" + ",".join(
             [str(show) for show in
              Quality.DOWNLOADED + [ARCHIVED]]) + ") AND season != 0 and episode != 0 AND airdate <= " + today + "")[0][
             0]
+        stats["ep_snatched"] = myDB.select("SELECT COUNT(*) FROM tv_episodes WHERE status IN (" + ",".join(
+            [str(show) for show in
+             Quality.SNATCHED + Quality.SNATCHED_PROPER]) + ") AND season != 0 and episode != 0 AND airdate <= " + today + "")[0][
+            0]
         stats["ep_total"] = myDB.select(
             "SELECT COUNT(*) FROM tv_episodes WHERE season != 0 AND episode != 0 AND (airdate != 1 OR status IN (" + ",".join(
                 [str(show) for show in (Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER) + [
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index dd3593b79e35d4e4be3356d411c1f9cf219de9e6..2f991009569a83fd8990eee766a94b29e1cb6ae5 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -18,21 +18,14 @@
 
 from __future__ import with_statement
 
-import base64
-import inspect
 import traceback
-
 import os
-
 import time
 import urllib
 import re
 import datetime
-import random
-import sys
 
 import sickbeard
-
 from sickbeard import config, sab
 from sickbeard import clients
 from sickbeard import history, notifiers, processTV
@@ -46,26 +39,23 @@ from sickbeard import scene_exceptions
 from sickbeard import subtitles
 from sickbeard import network_timezones
 from sickbeard import sbdatetime
-
 from sickbeard.providers import newznab, rsstorrent
 from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets
 from sickbeard.common import SNATCHED, UNAIRED, IGNORED, ARCHIVED, WANTED, FAILED
 from sickbeard.common import SD, HD720p, HD1080p
 from sickbeard.exceptions import ex
+from sickbeard.blackandwhitelist import BlackAndWhiteList
 from sickbeard.scene_exceptions import get_scene_exceptions
+from sickbeard.browser import foldersAtPath
 from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering, get_scene_numbering_for_show, \
     get_xem_numbering_for_show, get_scene_absolute_numbering_for_show, get_xem_absolute_numbering_for_show, \
     get_scene_absolute_numbering
 
-from sickbeard.blackandwhitelist import BlackAndWhiteList
-
-from browser import WebFileBrowser
-
 from lib.dateutil import tz
 from lib.unrar2 import RarFile
-
 from lib import adba, subliminal
-from lib.trakt import TraktCall
+from lib.trakt import TraktAPI
+from lib.trakt.exceptions import traktException, traktAuthException, traktServerBusy
 
 try:
     import json
@@ -77,100 +67,102 @@ try:
 except ImportError:
     import xml.etree.ElementTree as etree
 
+from Cheetah.Template import Template as CheetahTemplate
+from Cheetah.Filters import Filter as CheetahFilter
 
-from Cheetah.Template import Template
-from tornado.web import RequestHandler, HTTPError, asynchronous
+from tornado.routes import route
+from tornado.web import RequestHandler, HTTPError, authenticated, asynchronous
+from tornado.gen import coroutine
+from tornado.ioloop import IOLoop
+from tornado.concurrent import run_on_executor
+from concurrent.futures import ThreadPoolExecutor
 
-from bug_tracker import BugTracker
+route_locks = {}
 
 
-def authenticated(handler_class):
-    def wrap_execute(handler_execute):
-        def basicauth(handler, transforms, *args, **kwargs):
-            def _request_basic_auth(handler):
-                handler.set_status(401)
-                handler.set_header('WWW-Authenticate', 'Basic realm="SickRage"')
-                handler._transforms = []
-                handler.finish()
-                return False
+class html_entities(CheetahFilter):
+    def filter(self, val, **dummy_kw):
+        if isinstance(val, unicode):
+            filtered = val.encode('ascii', 'xmlcharrefreplace')
+        elif val is None:
+            filtered = ''
+        elif isinstance(val, str):
+            filtered = val.decode(sickbeard.SYS_ENCODING).encode('ascii', 'xmlcharrefreplace')
+        else:
+            filtered = self.filter(str(val))
 
-            try:
-                if not (sickbeard.WEB_USERNAME and sickbeard.WEB_PASSWORD):
-                    return True
-                elif (handler.request.uri.startswith(sickbeard.WEB_ROOT + '/api') and
-                              '/api/builder' not in handler.request.uri):
-                    return True
-                elif (handler.request.uri.startswith(sickbeard.WEB_ROOT + '/calendar') and
-                          sickbeard.CALENDAR_UNPROTECTED):
-                    return True
-
-                auth_hdr = handler.request.headers.get('Authorization')
-
-                if auth_hdr == None:
-                    return _request_basic_auth(handler)
-                if not auth_hdr.startswith('Basic '):
-                    return _request_basic_auth(handler)
-
-                auth_decoded = base64.decodestring(auth_hdr[6:])
-                username, password = auth_decoded.split(':', 2)
-
-                if username != sickbeard.WEB_USERNAME or password != sickbeard.WEB_PASSWORD:
-                    return _request_basic_auth(handler)
-            except Exception, e:
-                return _request_basic_auth(handler)
-            return True
+        return filtered
 
-        def _execute(self, transforms, *args, **kwargs):
-            if not basicauth(self, transforms, *args, **kwargs):
-                return False
-            return handler_execute(self, transforms, *args, **kwargs)
 
-        return _execute
+class PageTemplate(CheetahTemplate):
+    def __init__(self, rh, *args, **kwargs):
+        kwargs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/", kwargs['file'])
+        kwargs['filter'] = html_entities
+        super(PageTemplate, self).__init__(*args, **kwargs)
 
-    handler_class._execute = wrap_execute(handler_class._execute)
-    return handler_class
+        self.sbRoot = sickbeard.WEB_ROOT
+        self.sbHttpPort = sickbeard.WEB_PORT
+        self.sbHttpsPort = sickbeard.WEB_PORT
+        self.sbHttpsEnabled = sickbeard.ENABLE_HTTPS
+        self.sbHandleReverseProxy = sickbeard.HANDLE_REVERSE_PROXY
+        self.sbThemeName = sickbeard.THEME_NAME
+        self.sbLogin = rh.get_current_user()
 
+        if rh.request.headers['Host'][0] == '[':
+            self.sbHost = re.match("^\[.*\]", rh.request.headers['Host'], re.X | re.M | re.S).group(0)
+        else:
+            self.sbHost = re.match("^[^:]+", rh.request.headers['Host'], re.X | re.M | re.S).group(0)
 
-class HTTPRedirect(Exception):
-    """Exception raised when the request should be redirected."""
+        if "X-Forwarded-Host" in rh.request.headers:
+            self.sbHost = rh.request.headers['X-Forwarded-Host']
+        if "X-Forwarded-Port" in rh.request.headers:
+            sbHttpPort = rh.request.headers['X-Forwarded-Port']
+            self.sbHttpsPort = sbHttpPort
+        if "X-Forwarded-Proto" in rh.request.headers:
+            self.sbHttpsEnabled = True if rh.request.headers['X-Forwarded-Proto'] == 'https' else False
 
-    def __init__(self, url, permanent=False, status=None):
-        self.url = url
-        self.permanent = permanent
-        self.status = status
-        Exception.__init__(self, self.url, self.permanent, self.status)
+        logPageTitle = 'Logs &amp; Errors'
+        if len(classes.ErrorViewer.errors):
+            logPageTitle += ' (' + str(len(classes.ErrorViewer.errors)) + ')'
+        self.logPageTitle = logPageTitle
+        self.sbPID = str(sickbeard.PID)
+        self.menu = [
+            {'title': 'Home', 'key': 'home'},
+            {'title': 'Coming Episodes', 'key': 'comingEpisodes'},
+            {'title': 'History', 'key': 'history'},
+            {'title': 'Manage', 'key': 'manage'},
+            {'title': 'Config', 'key': 'config'},
+            {'title': logPageTitle, 'key': 'errorlogs'},
+        ]
 
-    def __call__(self):
-        """Use this exception as a request.handler (raise self)."""
-        raise self
+    def compile(self, *args, **kwargs):
+        if not os.path.exists(os.path.join(sickbeard.CACHE_DIR, 'cheetah')):
+            os.mkdir(os.path.join(sickbeard.CACHE_DIR, 'cheetah'))
 
+        kwargs['cacheModuleFilesForTracebacks'] = True
+        kwargs['cacheDirForModuleFiles'] = os.path.join(sickbeard.CACHE_DIR, 'cheetah')
+        return super(PageTemplate, self).compile(*args, **kwargs)
 
-def redirect(url, permanent=False, status=None):
-    assert url[0] == '/'
-    raise HTTPRedirect(sickbeard.WEB_ROOT + url, permanent, status)
 
+class BaseHandler(RequestHandler):
+    def __init__(self, *args, **kwargs):
+        super(BaseHandler, self).__init__(*args, **kwargs)
 
-@authenticated
-class MainHandler(RequestHandler):
-    def http_error_401_handler(self):
-        """ Custom handler for 401 error """
-        return r'''<!DOCTYPE html>
-    <html>
-        <head>
-            <title>%s</title>
-        </head>
-        <body>
-            <br/>
-            <font color="#0000FF">Error %s: You need to provide a valid username and password.</font>
-        </body>
-    </html>
-    ''' % ('Access denied', 401)
+    def set_default_headers(self):
+        self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
 
     def write_error(self, status_code, **kwargs):
-        if status_code == 401:
-            self.finish(self.http_error_401_handler())
-        elif status_code == 404:
-            self.redirect(sickbeard.WEB_ROOT + '/home/')
+        # handle 404 http errors
+        if status_code == 404:
+            url = self.request.uri
+            if self.request.uri.startswith(sickbeard.WEB_ROOT):
+                url = url[len(sickbeard.WEB_ROOT) + 1:]
+
+            if url[:3] != 'api':
+                return self.redirect(url)
+            else:
+                self.finish('Wrong API key used')
+
         elif self.settings.get("debug") and "exc_info" in kwargs:
             exc_info = kwargs["exc_info"]
             trace_info = ''.join(["%s<br/>" % line for line in traceback.format_exception(*exc_info)])
@@ -192,71 +184,173 @@ class MainHandler(RequestHandler):
                                </html>""" % (error, error,
                                              trace_info, request_info))
 
-    def _dispatch(self):
-        path = self.request.uri.replace(sickbeard.WEB_ROOT, '').split('?')[0]
+    def redirect(self, url, permanent=False, status=None):
+        if not url.startswith(sickbeard.WEB_ROOT):
+            url = sickbeard.WEB_ROOT + url
 
-        method = path.strip('/').split('/')[-1]
+        super(BaseHandler, self).redirect(url, permanent, status)
+
+    def get_current_user(self, *args, **kwargs):
+        if not isinstance(self, UI) and sickbeard.WEB_USERNAME and sickbeard.WEB_PASSWORD:
+            return self.get_secure_cookie('user')
+        else:
+            return True
 
-        if method == 'robots.txt':
-            method = 'robots_txt'
 
-        if path.startswith('/api') and method != 'builder':
-            apikey = path.strip('/').split('/')[-1]
-            method = path.strip('/').split('/')[0]
-            self.request.arguments.update({'apikey': [apikey]})
+class WebHandler(BaseHandler):
+    def __init__(self, *args, **kwargs):
+        super(WebHandler, self).__init__(*args, **kwargs)
 
-        def pred(c):
-            return inspect.isclass(c) and c.__module__ == pred.__module__
+    io_loop = IOLoop.current()
+    executor = ThreadPoolExecutor(50)
 
+    @coroutine
+    @asynchronous
+    @authenticated
+    def get(self, route, *args, **kwargs):
         try:
-            klass = [cls[1] for cls in
-                     inspect.getmembers(sys.modules[__name__], pred) + [(self.__class__.__name__, self.__class__)] if
-                     cls[0].lower() == method.lower() or method in cls[1].__dict__.keys()][0](self.application,
-                                                                                              self.request)
+            # route -> method obj
+            route = route.strip('/').replace('.', '_') or 'index'
+            method = getattr(self, route)
+
+            # process request async
+            self.async_call(method, callback=self.async_done)
         except:
-            klass = None
+            logger.log('Failed doing webui request "%s": %s' % (route, traceback.format_exc()), logger.ERROR)
+            raise HTTPError(404)
 
-        if klass and not method.startswith('_'):
-            # Sanitize argument lists:
-            args = self.request.arguments
-            for arg, value in args.items():
+    @run_on_executor
+    def async_call(self, function, callback=None):
+        try:
+            kwargs = self.request.arguments
+            for arg, value in kwargs.items():
                 if len(value) == 1:
-                    args[arg] = value[0]
+                    kwargs[arg] = value[0]
 
-            # Regular method handler for classes
-            func = getattr(klass, method, None)
+            result = function(**kwargs)
+            if callback:
+                callback(result)
+            return result
+        except:
+            logger.log('Failed doing webui callback: %s' % (traceback.format_exc()), logger.ERROR)
+            raise
+
+    def async_done(self, results):
+        try:
+            if results:
+                self.finish(results)
+        except:
+            logger.log('Failed sending webui reponse: %s' % (traceback.format_exc()), logger.DEBUG)
+            raise
 
-            # Special index method handler for classes and subclasses:
-            if path.startswith('/api') or path.endswith('/'):
-                if func and getattr(func, 'index', None):
-                    func = getattr(func(self.application, self.request), 'index', None)
-                elif not func:
-                    func = getattr(klass, 'index', None)
+    # post uses get method
+    post = get
 
-            if callable(func):
-                return func(**args)
 
-        raise HTTPError(404)
+class LoginHandler(BaseHandler):
+    def __init__(self, *args, **kwargs):
+        super(LoginHandler, self).__init__(*args, **kwargs)
 
-    @asynchronous
     def get(self, *args, **kwargs):
-        try:
-            self.finish(self._dispatch())
-        except HTTPRedirect, e:
-            self.redirect(e.url, e.permanent, e.status)
+        if self.get_current_user():
+            self.redirect('/home/')
+        else:
+            t = PageTemplate(rh=self, file="login.tmpl")
+            self.finish(t.respond())
 
-    @asynchronous
     def post(self, *args, **kwargs):
+
+        api_key = None
+
+        username = sickbeard.WEB_USERNAME
+        password = sickbeard.WEB_PASSWORD
+
+        if (self.get_argument('username') == username or not username) \
+                and (self.get_argument('password') == password or not password):
+            api_key = sickbeard.API_KEY
+
+        if api_key:
+            remember_me = int(self.get_argument('remember_me', default=0) or 0)
+            self.set_secure_cookie('user', api_key, expires_days=30 if remember_me > 0 else None)
+
+        self.redirect('/home/')
+
+
+class LogoutHandler(BaseHandler):
+    def __init__(self, *args, **kwargs):
+        super(LogoutHandler, self).__init__(*args, **kwargs)
+
+    def get(self, *args, **kwargs):
+        self.clear_cookie("user")
+        self.redirect('/login/')
+
+
+class KeyHandler(BaseHandler):
+    def __init__(self, *args, **kwargs):
+        super(KeyHandler, self).__init__(*args, **kwargs)
+
+    def get(self, *args, **kwargs):
+        api_key = None
+
         try:
-            self.finish(self._dispatch())
-        except HTTPRedirect, e:
-            self.redirect(e.url, e.permanent, e.status)
+            username = sickbeard.WEB_USERNAME
+            password = sickbeard.WEB_PASSWORD
+
+            if (self.get_argument('u', None) == username or not username) and \
+                    (self.get_argument('p', None) == password or not password):
+                api_key = sickbeard.API_KEY
 
-    def robots_txt(self, *args, **kwargs):
+            self.finish({'success': api_key is not None, 'api_key': api_key})
+        except:
+            logger.log('Failed doing key request: %s' % (traceback.format_exc()), logger.ERROR)
+            self.finish({'success': False, 'error': 'Failed returning results'})
+
+
+@route('(.*)(/?)')
+class WebRoot(WebHandler):
+    def __init__(self, *args, **kwargs):
+        super(WebRoot, self).__init__(*args, **kwargs)
+
+    def index(self):
+        return self.redirect('/home/')
+
+    def robots_txt(self):
         """ Keep web crawlers out """
         self.set_header('Content-Type', 'text/plain')
         return "User-agent: *\nDisallow: /"
 
+    def apibuilder(self):
+        t = PageTemplate(rh=self, file="apiBuilder.tmpl")
+
+        def titler(x):
+            return (helpers.remove_article(x), x)[not x or sickbeard.SORT_ARTICLE]
+
+        t.sortedShowList = sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))
+
+        myDB = db.DBConnection(row_type="dict")
+
+        seasonSQLResults = {}
+        episodeSQLResults = {}
+
+        for curShow in t.sortedShowList:
+            seasonSQLResults[curShow.indexerid] = myDB.select(
+                "SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC", [curShow.indexerid])
+
+        for curShow in t.sortedShowList:
+            episodeSQLResults[curShow.indexerid] = myDB.select(
+                "SELECT DISTINCT season,episode FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC",
+                [curShow.indexerid])
+
+        t.seasonSQLResults = seasonSQLResults
+        t.episodeSQLResults = episodeSQLResults
+
+        if len(sickbeard.API_KEY) == 32:
+            t.apikey = sickbeard.API_KEY
+        else:
+            t.apikey = "api key not generated"
+
+        return t.respond()
+
     def showPoster(self, show=None, which=None):
         # Redirect initial poster/banner thumb to default images
         if which[0:6] == 'poster':
@@ -264,7 +358,8 @@ class MainHandler(RequestHandler):
         else:
             default_image_name = 'banner.png'
 
-        image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', default_image_name)
+        # image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', default_image_name)
+        static_image_path = os.path.join('/images', default_image_name)
         if show and sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)):
             cache_obj = image_cache.ImageCache()
 
@@ -279,13 +374,10 @@ class MainHandler(RequestHandler):
                 image_file_name = cache_obj.banner_thumb_path(show)
 
             if ek.ek(os.path.isfile, image_file_name):
-                image_path = image_file_name
+                static_image_path = os.path.normpath(image_file_name.replace(sickbeard.CACHE_DIR, '/cache'))
 
-        from mimetypes import MimeTypes
-        mime_type, encoding = MimeTypes().guess_type(image_path)
-        self.set_header('Content-Type', mime_type)
-        with file(image_path, 'rb') as img:
-            return img.read()
+        static_image_path = static_image_path.replace('\\', '/')
+        return self.redirect(static_image_path)
 
     def setHomeLayout(self, layout):
 
@@ -294,7 +386,7 @@ class MainHandler(RequestHandler):
 
         sickbeard.HOME_LAYOUT = layout
 
-        redirect("/home/")
+        return self.redirect("/home/")
 
     def setPosterSortBy(self, sort):
 
@@ -316,13 +408,13 @@ class MainHandler(RequestHandler):
 
         sickbeard.HISTORY_LAYOUT = layout
 
-        redirect("/history/")
+        return self.redirect("/history/")
 
     def toggleDisplayShowSpecials(self, show):
 
         sickbeard.DISPLAY_SHOW_SPECIALS = not sickbeard.DISPLAY_SHOW_SPECIALS
 
-        redirect("/home/displayShow?show=" + show)
+        return self.redirect("/home/displayShow?show=" + show)
 
     def setComingEpsLayout(self, layout):
         if layout not in ('poster', 'banner', 'list', 'calendar'):
@@ -333,24 +425,25 @@ class MainHandler(RequestHandler):
 
         sickbeard.COMING_EPS_LAYOUT = layout
 
-        redirect("/comingEpisodes/")
+        return self.redirect("/comingEpisodes/")
 
-    def toggleComingEpsDisplayPaused(self, *args, **kwargs):
+    def toggleComingEpsDisplayPaused(self):
 
         sickbeard.COMING_EPS_DISPLAY_PAUSED = not sickbeard.COMING_EPS_DISPLAY_PAUSED
 
-        redirect("/comingEpisodes/")
+        return self.redirect("/comingEpisodes/")
 
     def setComingEpsSort(self, sort):
         if sort not in ('date', 'network', 'show'):
             sort = 'date'
 
         if sickbeard.COMING_EPS_LAYOUT == 'calendar':
-            sort = 'date'
+            sort \
+                = 'date'
 
         sickbeard.COMING_EPS_SORT = sort
 
-        redirect("/comingEpisodes/")
+        return self.redirect("/comingEpisodes/")
 
     def comingEpisodes(self, layout="None"):
 
@@ -365,14 +458,14 @@ class MainHandler(RequestHandler):
 
         myDB = db.DBConnection()
         sql_results = myDB.select(
-            "SELECT *, tv_shows.status as show_status FROM tv_episodes, tv_shows WHERE season != 0 AND airdate >= ? AND airdate < ? AND tv_shows.indexer_id = tv_episodes.showid AND tv_episodes.status NOT IN (" + ','.join(
+            "SELECT *, tv_shows.status AS show_status FROM tv_episodes, tv_shows WHERE season != 0 AND airdate >= ? AND airdate < ? AND tv_shows.indexer_id = tv_episodes.showid AND tv_episodes.status NOT IN (" + ','.join(
                 ['?'] * len(qualList)) + ")", [today, next_week] + qualList)
 
         for cur_result in sql_results:
             done_show_list.append(int(cur_result["showid"]))
 
         more_sql_results = myDB.select(
-            "SELECT *, tv_shows.status as show_status FROM tv_episodes outer_eps, tv_shows WHERE season != 0 AND showid NOT IN (" + ','.join(
+            "SELECT *, tv_shows.status AS show_status FROM tv_episodes outer_eps, tv_shows WHERE season != 0 AND showid NOT IN (" + ','.join(
                 ['?'] * len(
                     done_show_list)) + ") AND tv_shows.indexer_id = outer_eps.showid AND airdate = (SELECT airdate FROM tv_episodes inner_eps WHERE inner_eps.season != 0 AND inner_eps.showid = outer_eps.showid AND inner_eps.airdate >= ? ORDER BY inner_eps.airdate ASC LIMIT 1) AND outer_eps.status NOT IN (" + ','.join(
                 ['?'] * len(Quality.DOWNLOADED + Quality.SNATCHED)) + ")",
@@ -380,7 +473,7 @@ class MainHandler(RequestHandler):
         sql_results += more_sql_results
 
         more_sql_results = myDB.select(
-            "SELECT *, tv_shows.status as show_status FROM tv_episodes, tv_shows WHERE season != 0 AND tv_shows.indexer_id = tv_episodes.showid AND airdate < ? AND airdate >= ? AND tv_episodes.status = ? AND tv_episodes.status NOT IN (" + ','.join(
+            "SELECT *, tv_shows.status AS show_status FROM tv_episodes, tv_shows WHERE season != 0 AND tv_shows.indexer_id = tv_episodes.showid AND airdate < ? AND airdate >= ? AND tv_episodes.status = ? AND tv_episodes.status NOT IN (" + ','.join(
                 ['?'] * len(qualList)) + ")", [today, recently, WANTED] + qualList)
         sql_results += more_sql_results
 
@@ -402,7 +495,7 @@ class MainHandler(RequestHandler):
 
         sql_results.sort(sorts[sickbeard.COMING_EPS_SORT])
 
-        t = PageTemplate(headers=self.request.headers, file="comingEpisodes.tmpl")
+        t = PageTemplate(rh=self, file="comingEpisodes.tmpl")
         # paused_item = { 'title': '', 'path': 'toggleComingEpsDisplayPaused' }
         # paused_item['title'] = 'Hide Paused' if sickbeard.COMING_EPS_DISPLAY_PAUSED else 'Show Paused'
         paused_item = {'title': 'View Paused:', 'path': {'': ''}}
@@ -432,13 +525,13 @@ class MainHandler(RequestHandler):
         else:
             t.layout = sickbeard.COMING_EPS_LAYOUT
 
-        return _munge(t)
+        return t.respond()
 
     # Raw iCalendar implementation by Pedro Jose Pereira Vieito (@pvieito).
     #
     # iCalendar (iCal) - Standard RFC 5545 <http://tools.ietf.org/html/rfc5546>
     # Works with iCloud, Google Calendar and Outlook.
-    def calendar(self, *args, **kwargs):
+    def calendar(self):
         """ Provides a subscribeable URL for iCal subscriptions
         """
 
@@ -500,4158 +593,4117 @@ class MainHandler(RequestHandler):
 
         return ical
 
-    def _genericMessage(self, subject, message):
-        t = PageTemplate(headers=self.request.headers, file="genericMessage.tmpl")
-        t.submenu = HomeMenu()
-        t.subject = subject
-        t.message = message
-        return _munge(t)
 
-    browser = WebFileBrowser
+@route('/ui(/?.*)')
+class UI(WebRoot):
+    def __init__(self, *args, **kwargs):
+        super(UI, self).__init__(*args, **kwargs)
 
+    def add_message(self):
+        ui.notifications.message('Test 1', 'This is test number 1')
+        ui.notifications.error('Test 2', 'This is test number 2')
 
-class PageTemplate(Template):
-    def __init__(self, headers, *args, **KWs):
-        KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/",
-                                   KWs['file'])
-        super(PageTemplate, self).__init__(*args, **KWs)
+        return "ok"
 
-        self.sbRoot = sickbeard.WEB_ROOT
-        self.sbHttpPort = sickbeard.WEB_PORT
-        self.sbHttpsPort = sickbeard.WEB_PORT
-        self.sbHttpsEnabled = sickbeard.ENABLE_HTTPS
-        self.sbHandleReverseProxy = sickbeard.HANDLE_REVERSE_PROXY
-        self.sbThemeName = sickbeard.THEME_NAME
+    def get_messages(self):
+        messages = {}
+        cur_notification_num = 1
+        for cur_notification in ui.notifications.get_notifications(self.request.remote_ip):
+            messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title,
+                                                                     'message': cur_notification.message,
+                                                                     'type': cur_notification.type}
+            cur_notification_num += 1
 
-        if headers['Host'][0] == '[':
-            self.sbHost = re.match("^\[.*\]", headers['Host'], re.X | re.M | re.S).group(0)
-        else:
-            self.sbHost = re.match("^[^:]+", headers['Host'], re.X | re.M | re.S).group(0)
+        return json.dumps(messages)
 
-        if "X-Forwarded-Host" in headers:
-            self.sbHost = headers['X-Forwarded-Host']
-        if "X-Forwarded-Port" in headers:
-            sbHttpPort = headers['X-Forwarded-Port']
-            self.sbHttpsPort = sbHttpPort
-        if "X-Forwarded-Proto" in headers:
-            self.sbHttpsEnabled = True if headers['X-Forwarded-Proto'] == 'https' else False
 
-        logPageTitle = 'Logs &amp; Errors'
-        if len(classes.ErrorViewer.errors):
-            logPageTitle += ' (' + str(len(classes.ErrorViewer.errors)) + ')'
-        self.logPageTitle = logPageTitle
-        self.sbPID = str(sickbeard.PID)
-        self.menu = [
-            {'title': 'Home', 'key': 'home'},
-            {'title': 'Coming Episodes', 'key': 'comingEpisodes'},
-            {'title': 'History', 'key': 'history'},
-            {'title': 'Manage', 'key': 'manage'},
-            {'title': 'Config', 'key': 'config'},
-            {'title': logPageTitle, 'key': 'errorlogs'},
-        ]
+@route('/browser(/?.*)')
+class WebFileBrowser(WebRoot):
+    def __init__(self, *args, **kwargs):
+        super(WebFileBrowser, self).__init__(*args, **kwargs)
 
-    def compile(self, *args, **kwargs):
-        if not os.path.exists(os.path.join(sickbeard.CACHE_DIR, 'cheetah')):
-            os.mkdir(os.path.join(sickbeard.CACHE_DIR, 'cheetah'))
+    def index(self, path='', includeFiles=False, *args, **kwargs):
+        self.set_header("Content-Type", "application/json")
+        return json.dumps(foldersAtPath(path, True, bool(int(includeFiles))))
 
-        kwargs['cacheModuleFilesForTracebacks'] = True
-        kwargs['cacheDirForModuleFiles'] = os.path.join(sickbeard.CACHE_DIR, 'cheetah')
-        return super(PageTemplate, self).compile(*args, **kwargs)
+    def complete(self, term, includeFiles=0, *args, **kwargs):
+        self.set_header("Content-Type", "application/json")
+        paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term), includeFiles=bool(int(includeFiles)))
+                 if 'path' in entry]
 
+        return json.dumps(paths)
 
-class IndexerWebUI(MainHandler):
-    def __init__(self, config, log=None):
-        self.config = config
-        self.log = log
 
-    def selectSeries(self, allSeries):
-        searchList = ",".join([x['id'] for x in allSeries])
-        showDirList = ""
-        for curShowDir in self.config['_showDir']:
-            showDirList += "showDir=" + curShowDir + "&"
-        redirect("/home/addShows/addShow?" + showDirList + "seriesList=" + searchList)
+@route('/home(/?.*)')
+class Home(WebRoot):
+    def __init__(self, *args, **kwargs):
+        super(Home, self).__init__(*args, **kwargs)
 
+    def HomeMenu(self):
+        menu = [
+            {'title': 'Add Shows', 'path': 'home/addShows/', },
+            {'title': 'Manual Post-Processing', 'path': 'home/postprocess/'},
+            {'title': 'Update KODI', 'path': 'home/updateKODI/', 'requires': self.haveKODI},
+            {'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': self.havePLEX},
+            {'title': 'Manage Torrents', 'path': 'manage/manageTorrents/', 'requires': self.haveTORRENT},
+        ]
 
-def _munge(string):
-    return unicode(string).encode('utf-8', 'xmlcharrefreplace')
+        return menu
 
+    def _genericMessage(self, subject, message):
+        t = PageTemplate(rh=self, file="genericMessage.tmpl")
+        t.submenu = self.HomeMenu()
+        t.subject = subject
+        t.message = message
+        return t.respond()
 
-def _getEpisode(show, season=None, episode=None, absolute=None):
-    if show is None:
-        return "Invalid show parameters"
+    def _getEpisode(self, show, season=None, episode=None, absolute=None):
+        if show is None:
+            return "Invalid show parameters"
 
-    showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-    if showObj is None:
-        return "Invalid show paramaters"
+        if showObj is None:
+            return "Invalid show paramaters"
 
-    if absolute:
-        epObj = showObj.getEpisode(absolute_number=int(absolute))
-    elif season and episode:
-        epObj = showObj.getEpisode(int(season), int(episode))
-    else:
-        return "Invalid paramaters"
+        if absolute:
+            epObj = showObj.getEpisode(absolute_number=int(absolute))
+        elif season and episode:
+            epObj = showObj.getEpisode(int(season), int(episode))
+        else:
+            return "Invalid paramaters"
 
-    if epObj is None:
-        return "Episode couldn't be retrieved"
+        if epObj is None:
+            return "Episode couldn't be retrieved"
 
-    return epObj
+        return epObj
 
+    def index(self):
+        t = PageTemplate(rh=self, file="home.tmpl")
+        if sickbeard.ANIME_SPLIT_HOME:
+            shows = []
+            anime = []
+            for show in sickbeard.showList:
+                if show.is_anime:
+                    anime.append(show)
+                else:
+                    shows.append(show)
+            t.showlists = [["Shows", shows],
+                           ["Anime", anime]]
+        else:
+            t.showlists = [["Shows", sickbeard.showList]]
 
-def ManageMenu():
-    manageMenu = [
-        {'title': 'Backlog Overview', 'path': 'manage/backlogOverview/'},
-        {'title': 'Manage Searches', 'path': 'manage/manageSearches/'},
-        {'title': 'Episode Status Management', 'path': 'manage/episodeStatuses/'}, ]
+        t.submenu = self.HomeMenu()
 
-    if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \
-            and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https'
-                 or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'):
-        manageMenu.append({'title': 'Manage Torrents', 'path': 'manage/manageTorrents/'})
+        return t.respond()
 
-    if sickbeard.USE_SUBTITLES:
-        manageMenu.append({'title': 'Missed Subtitle Management', 'path': 'manage/subtitleMissed/'})
+    def is_alive(self, *args, **kwargs):
+        if 'callback' in kwargs and '_' in kwargs:
+            callback, _ = kwargs['callback'], kwargs['_']
+        else:
+            return "Error: Unsupported Request. Send jsonp request with 'callback' variable in the query string."
 
-    if sickbeard.USE_FAILED_DOWNLOADS:
-        manageMenu.append({'title': 'Failed Downloads', 'path': 'manage/failedDownloads/'})
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        self.set_header('Content-Type', 'text/javascript')
+        self.set_header('Access-Control-Allow-Origin', '*')
+        self.set_header('Access-Control-Allow-Headers', 'x-requested-with')
 
-    return manageMenu
+        if sickbeard.started:
+            return callback + '(' + json.dumps(
+                {"msg": str(sickbeard.PID)}) + ');'
+        else:
+            return callback + '(' + json.dumps({"msg": "nope"}) + ');'
 
+    def haveKODI(self):
+        return sickbeard.USE_KODI and sickbeard.KODI_UPDATE_LIBRARY
 
-class ManageSearches(MainHandler):
-    def index(self, *args, **kwargs):
-        t = PageTemplate(headers=self.request.headers, file="manage_manageSearches.tmpl")
-        # t.backlogPI = sickbeard.backlogSearchScheduler.action.getProgressIndicator()
-        t.backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused()  # @UndefinedVariable
-        t.backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress()  # @UndefinedVariable
-        t.dailySearchStatus = sickbeard.dailySearchScheduler.action.amActive  # @UndefinedVariable
-        t.findPropersStatus = sickbeard.properFinderScheduler.action.amActive  # @UndefinedVariable
-        t.queueLength = sickbeard.searchQueueScheduler.action.queue_length()
+    def havePLEX(self):
+        return sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY
 
-        t.submenu = ManageMenu()
+    def haveTORRENT(self):
+        if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \
+                and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https'
+                     or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'):
+            return True
+        else:
+            return False
 
-        return _munge(t)
+    def testSABnzbd(self, host=None, username=None, password=None, apikey=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
+        host = config.clean_url(host)
 
-    def forceVersionCheck(self, *args, **kwargs):
-        # force a check to see if there is a new version
-        if sickbeard.versionCheckScheduler.action.check_for_new_version(force=True):
-            logger.log(u"Forcing version check")
+        connection, accesMsg = sab.getSabAccesMethod(host, username, password, apikey)
+        if connection:
+            authed, authMsg = sab.testAuthentication(host, username, password, apikey)  # @UnusedVariable
+            if authed:
+                return "Success. Connected and authenticated"
+            else:
+                return "Authentication failed. SABnzbd expects '" + accesMsg + "' as authentication method"
+        else:
+            return "Unable to connect to host"
 
-        redirect("/home/")
 
-    def forceBacklog(self, *args, **kwargs):
-        # force it to run the next time it looks
-        result = sickbeard.backlogSearchScheduler.forceRun()
-        if result:
-            logger.log(u"Backlog search forced")
-            ui.notifications.message('Backlog search started')
+    def testTorrent(self, torrent_method=None, host=None, username=None, password=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        redirect("/manage/manageSearches/")
+        host = config.clean_url(host)
 
-    def forceSearch(self, *args, **kwargs):
+        client = clients.getClientIstance(torrent_method)
 
-        # force it to run the next time it looks
-        result = sickbeard.dailySearchScheduler.forceRun()
-        if result:
-            logger.log(u"Daily search forced")
-            ui.notifications.message('Daily search started')
+        connection, accesMsg = client(host, username, password).testAuthentication()
 
-        redirect("/manage/manageSearches/")
+        return accesMsg
 
 
-    def forceFindPropers(self, *args, **kwargs):
+    def testGrowl(self, host=None, password=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+
+        host = config.clean_host(host, default_port=23053)
+
+        result = notifiers.growl_notifier.test_notify(host, password)
+        if password is None or password == '':
+            pw_append = ''
+        else:
+            pw_append = " with password: " + password
 
-        # force it to run the next time it looks
-        result = sickbeard.properFinderScheduler.forceRun()
         if result:
-            logger.log(u"Find propers search forced")
-            ui.notifications.message('Find propers search started')
+            return "Registered and Tested growl successfully " + urllib.unquote_plus(host) + pw_append
+        else:
+            return "Registration and Testing of growl failed " + urllib.unquote_plus(host) + pw_append
 
-        redirect("/manage/manageSearches/")
 
+    def testProwl(self, prowl_api=None, prowl_priority=0):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-    def pauseBacklog(self, paused=None):
-        if paused == "1":
-            sickbeard.searchQueueScheduler.action.pause_backlog()  # @UndefinedVariable
+        result = notifiers.prowl_notifier.test_notify(prowl_api, prowl_priority)
+        if result:
+            return "Test prowl notice sent successfully"
         else:
-            sickbeard.searchQueueScheduler.action.unpause_backlog()  # @UndefinedVariable
+            return "Test prowl notice failed"
 
-        redirect("/manage/manageSearches/")
 
+    def testBoxcar(self, username=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-class Manage(MainHandler):
-    def index(self, *args, **kwargs):
-        t = PageTemplate(headers=self.request.headers, file="manage.tmpl")
-        t.submenu = ManageMenu()
-        return _munge(t)
+        result = notifiers.boxcar_notifier.test_notify(username)
+        if result:
+            return "Boxcar notification succeeded. Check your Boxcar clients to make sure it worked"
+        else:
+            return "Error sending Boxcar notification"
 
 
-    def showEpisodeStatuses(self, indexer_id, whichStatus):
-        status_list = [int(whichStatus)]
-        if status_list[0] == SNATCHED:
-            status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
+    def testBoxcar2(self, accesstoken=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        myDB = db.DBConnection()
-        cur_show_results = myDB.select(
-            "SELECT season, episode, name FROM tv_episodes WHERE showid = ? AND season != 0 AND status IN (" + ','.join(
-                ['?'] * len(status_list)) + ")", [int(indexer_id)] + status_list)
+        result = notifiers.boxcar2_notifier.test_notify(accesstoken)
+        if result:
+            return "Boxcar2 notification succeeded. Check your Boxcar2 clients to make sure it worked"
+        else:
+            return "Error sending Boxcar2 notification"
 
-        result = {}
-        for cur_result in cur_show_results:
-            cur_season = int(cur_result["season"])
-            cur_episode = int(cur_result["episode"])
 
-            if cur_season not in result:
-                result[cur_season] = {}
+    def testPushover(self, userKey=None, apiKey=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-            result[cur_season][cur_episode] = cur_result["name"]
+        result = notifiers.pushover_notifier.test_notify(userKey, apiKey)
+        if result:
+            return "Pushover notification succeeded. Check your Pushover clients to make sure it worked"
+        else:
+            return "Error sending Pushover notification"
 
-        return json.dumps(result)
 
+    def twitterStep1(self):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-    def episodeStatuses(self, whichStatus=None):
+        return notifiers.twitter_notifier._get_authorization()
 
-        if whichStatus:
-            whichStatus = int(whichStatus)
-            status_list = [whichStatus]
-            if status_list[0] == SNATCHED:
-                status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
+
+    def twitterStep2(self, key):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+
+        result = notifiers.twitter_notifier._get_credentials(key)
+        logger.log(u"result: " + str(result))
+        if result:
+            return "Key verification successful"
         else:
-            status_list = []
+            return "Unable to verify key"
 
-        t = PageTemplate(headers=self.request.headers, file="manage_episodeStatuses.tmpl")
-        t.submenu = ManageMenu()
-        t.whichStatus = whichStatus
 
-        # if we have no status then this is as far as we need to go
-        if not status_list:
-            return _munge(t)
+    def testTwitter(self):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        myDB = db.DBConnection()
-        status_results = myDB.select(
-            "SELECT show_name, tv_shows.indexer_id as indexer_id FROM tv_episodes, tv_shows WHERE tv_episodes.status IN (" + ','.join(
-                ['?'] * len(
-                    status_list)) + ") AND season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name",
-            status_list)
+        result = notifiers.twitter_notifier.test_notify()
+        if result:
+            return "Tweet successful, check your twitter to make sure it worked"
+        else:
+            return "Error sending tweet"
 
-        ep_counts = {}
-        show_names = {}
-        sorted_show_ids = []
-        for cur_status_result in status_results:
-            cur_indexer_id = int(cur_status_result["indexer_id"])
-            if cur_indexer_id not in ep_counts:
-                ep_counts[cur_indexer_id] = 1
+
+    def testKODI(self, host=None, username=None, password=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+
+        host = config.clean_hosts(host)
+        finalResult = ''
+        for curHost in [x.strip() for x in host.split(",")]:
+            curResult = notifiers.kodi_notifier.test_notify(urllib.unquote_plus(curHost), username, password)
+            if len(curResult.split(":")) > 2 and 'OK' in curResult.split(":")[2]:
+                finalResult += "Test KODI notice sent successfully to " + urllib.unquote_plus(curHost)
             else:
-                ep_counts[cur_indexer_id] += 1
+                finalResult += "Test KODI notice failed to " + urllib.unquote_plus(curHost)
+            finalResult += "<br />\n"
 
-            show_names[cur_indexer_id] = cur_status_result["show_name"]
-            if cur_indexer_id not in sorted_show_ids:
-                sorted_show_ids.append(cur_indexer_id)
+        return finalResult
 
-        t.show_names = show_names
-        t.ep_counts = ep_counts
-        t.sorted_show_ids = sorted_show_ids
-        return _munge(t)
 
+    def testPLEX(self, host=None, username=None, password=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-    def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs):
+        finalResult = ''
+        for curHost in [x.strip() for x in host.split(",")]:
+            curResult = notifiers.plex_notifier.test_notify(urllib.unquote_plus(curHost), username, password)
+            if len(curResult.split(":")) > 2 and 'OK' in curResult.split(":")[2]:
+                finalResult += "Test Plex notice sent successfully to " + urllib.unquote_plus(curHost)
+            else:
+                finalResult += "Test Plex notice failed to " + urllib.unquote_plus(curHost)
+            finalResult += "<br />\n"
 
-        status_list = [int(oldStatus)]
-        if status_list[0] == SNATCHED:
-            status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
+        return finalResult
 
-        to_change = {}
 
-        # make a list of all shows and their associated args
-        for arg in kwargs:
-            indexer_id, what = arg.split('-')
+    def testLibnotify(self):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-            # we don't care about unchecked checkboxes
-            if kwargs[arg] != 'on':
-                continue
+        if notifiers.libnotify_notifier.test_notify():
+            return "Tried sending desktop notification via libnotify"
+        else:
+            return notifiers.libnotify.diagnose()
 
-            if indexer_id not in to_change:
-                to_change[indexer_id] = []
 
-            to_change[indexer_id].append(what)
+    def testNMJ(self, host=None, database=None, mount=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        myDB = db.DBConnection()
-        for cur_indexer_id in to_change:
+        host = config.clean_host(host)
+        result = notifiers.nmj_notifier.test_notify(urllib.unquote_plus(host), database, mount)
+        if result:
+            return "Successfully started the scan update"
+        else:
+            return "Test failed to start the scan update"
 
-            # get a list of all the eps we want to change if they just said "all"
-            if 'all' in to_change[cur_indexer_id]:
-                all_eps_results = myDB.select(
-                    "SELECT season, episode FROM tv_episodes WHERE status IN (" + ','.join(
-                        ['?'] * len(status_list)) + ") AND season != 0 AND showid = ?",
-                    status_list + [cur_indexer_id])
-                all_eps = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results]
-                to_change[cur_indexer_id] = all_eps
 
-            Home(self.application, self.request).setStatus(cur_indexer_id, '|'.join(to_change[cur_indexer_id]),
-                                                           newStatus, direct=True)
+    def settingsNMJ(self, host=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        redirect('/manage/episodeStatuses/')
+        host = config.clean_host(host)
+        result = notifiers.nmj_notifier.notify_settings(urllib.unquote_plus(host))
+        if result:
+            return '{"message": "Got settings from %(host)s", "database": "%(database)s", "mount": "%(mount)s"}' % {
+                "host": host, "database": sickbeard.NMJ_DATABASE, "mount": sickbeard.NMJ_MOUNT}
+        else:
+            return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}'
 
 
-    def showSubtitleMissed(self, indexer_id, whichSubs):
-        myDB = db.DBConnection()
-        cur_show_results = myDB.select(
-            "SELECT season, episode, name, subtitles FROM tv_episodes WHERE showid = ? AND season != 0 AND status LIKE '%4'",
-            [int(indexer_id)])
+    def testNMJv2(self, host=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        result = {}
-        for cur_result in cur_show_results:
-            if whichSubs == 'all':
-                if len(set(cur_result["subtitles"].split(',')).intersection(set(subtitles.wantedLanguages()))) >= len(
-                        subtitles.wantedLanguages()):
-                    continue
-            elif whichSubs in cur_result["subtitles"].split(','):
-                continue
+        host = config.clean_host(host)
+        result = notifiers.nmjv2_notifier.test_notify(urllib.unquote_plus(host))
+        if result:
+            return "Test notice sent successfully to " + urllib.unquote_plus(host)
+        else:
+            return "Test notice failed to " + urllib.unquote_plus(host)
 
-            cur_season = int(cur_result["season"])
-            cur_episode = int(cur_result["episode"])
 
-            if cur_season not in result:
-                result[cur_season] = {}
+    def settingsNMJv2(self, host=None, dbloc=None, instance=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-            if cur_episode not in result[cur_season]:
-                result[cur_season][cur_episode] = {}
+        host = config.clean_host(host)
+        result = notifiers.nmjv2_notifier.notify_settings(urllib.unquote_plus(host), dbloc, instance)
+        if result:
+            return '{"message": "NMJ Database found at: %(host)s", "database": "%(database)s"}' % {"host": host,
+                                                                                                   "database": sickbeard.NMJv2_DATABASE}
+        else:
+            return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {
+                "dbloc": dbloc}
 
-            result[cur_season][cur_episode]["name"] = cur_result["name"]
 
-            result[cur_season][cur_episode]["subtitles"] = ",".join(
-                subliminal.language.Language(subtitle).alpha2 for subtitle in cur_result["subtitles"].split(',')) if not \
-                cur_result["subtitles"] == '' else ''
+    def testTrakt(self, api=None, username=None, password=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        return notifiers.trakt_notifier.test_notify(api, username, password)
 
-        return json.dumps(result)
 
+    def loadShowNotifyLists(self):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-    def subtitleMissed(self, whichSubs=None):
+        myDB = db.DBConnection()
+        rows = myDB.select("SELECT show_id, show_name, notify_list FROM tv_shows ORDER BY show_name ASC")
 
-        t = PageTemplate(headers=self.request.headers, file="manage_subtitleMissed.tmpl")
-        t.submenu = ManageMenu()
-        t.whichSubs = whichSubs
+        data = {}
+        size = 0
+        for r in rows:
+            data[r['show_id']] = {'id': r['show_id'], 'name': r['show_name'], 'list': r['notify_list']}
+            size += 1
+        data['_size'] = size
+        return json.dumps(data)
 
-        if not whichSubs:
-            return _munge(t)
 
-        myDB = db.DBConnection()
-        status_results = myDB.select(
-            "SELECT show_name, tv_shows.indexer_id as indexer_id, tv_episodes.subtitles subtitles FROM tv_episodes, tv_shows WHERE tv_shows.subtitles = 1 AND tv_episodes.status LIKE '%4' AND tv_episodes.season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name")
+    def testEmail(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        ep_counts = {}
-        show_names = {}
-        sorted_show_ids = []
-        for cur_status_result in status_results:
-            if whichSubs == 'all':
-                if len(set(cur_status_result["subtitles"].split(',')).intersection(
-                        set(subtitles.wantedLanguages()))) >= len(subtitles.wantedLanguages()):
-                    continue
-            elif whichSubs in cur_status_result["subtitles"].split(','):
-                continue
+        host = config.clean_host(host)
+        if notifiers.email_notifier.test_notify(host, port, smtp_from, use_tls, user, pwd, to):
+            return 'Test email sent successfully! Check inbox.'
+        else:
+            return 'ERROR: %s' % notifiers.email_notifier.last_err
 
-            cur_indexer_id = int(cur_status_result["indexer_id"])
-            if cur_indexer_id not in ep_counts:
-                ep_counts[cur_indexer_id] = 1
-            else:
-                ep_counts[cur_indexer_id] += 1
 
-            show_names[cur_indexer_id] = cur_status_result["show_name"]
-            if cur_indexer_id not in sorted_show_ids:
-                sorted_show_ids.append(cur_indexer_id)
+    def testNMA(self, nma_api=None, nma_priority=0):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        t.show_names = show_names
-        t.ep_counts = ep_counts
-        t.sorted_show_ids = sorted_show_ids
-        return _munge(t)
+        result = notifiers.nma_notifier.test_notify(nma_api, nma_priority)
+        if result:
+            return "Test NMA notice sent successfully"
+        else:
+            return "Test NMA notice failed"
 
 
-    def downloadSubtitleMissed(self, *args, **kwargs):
+    def testPushalot(self, authorizationToken=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        to_download = {}
+        result = notifiers.pushalot_notifier.test_notify(authorizationToken)
+        if result:
+            return "Pushalot notification succeeded. Check your Pushalot clients to make sure it worked"
+        else:
+            return "Error sending Pushalot notification"
 
-        # make a list of all shows and their associated args
-        for arg in kwargs:
-            indexer_id, what = arg.split('-')
 
-            # we don't care about unchecked checkboxes
-            if kwargs[arg] != 'on':
-                continue
+    def testPushbullet(self, api=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-            if indexer_id not in to_download:
-                to_download[indexer_id] = []
+        result = notifiers.pushbullet_notifier.test_notify(api)
+        if result:
+            return "Pushbullet notification succeeded. Check your device to make sure it worked"
+        else:
+            return "Error sending Pushbullet notification"
 
-            to_download[indexer_id].append(what)
 
-        for cur_indexer_id in to_download:
-            # get a list of all the eps we want to download subtitles if they just said "all"
-            if 'all' in to_download[cur_indexer_id]:
-                myDB = db.DBConnection()
-                all_eps_results = myDB.select(
-                    "SELECT season, episode FROM tv_episodes WHERE status LIKE '%4' AND season != 0 AND showid = ?",
-                    [cur_indexer_id])
-                to_download[cur_indexer_id] = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results]
+    def getPushbulletDevices(self, api=None):
+        # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-            for epResult in to_download[cur_indexer_id]:
-                season, episode = epResult.split('x')
+        result = notifiers.pushbullet_notifier.get_devices(api)
+        if result:
+            return result
+        else:
+            return "Error sending Pushbullet notification"
 
-                show = sickbeard.helpers.findCertainShow(sickbeard.showList, int(cur_indexer_id))
-                subtitles = show.getEpisode(int(season), int(episode)).downloadSubtitles()
+    def shutdown(self, pid=None):
+        if str(pid) != str(sickbeard.PID):
+            return self.redirect("/home/")
 
-        redirect('/manage/subtitleMissed/')
+        sickbeard.events.put(sickbeard.events.SystemEvent.SHUTDOWN)
 
+        title = "Shutting down"
+        message = "SickRage is shutting down..."
 
-    def backlogShow(self, indexer_id):
+        return self._genericMessage(title, message)
 
-        show_obj = helpers.findCertainShow(sickbeard.showList, int(indexer_id))
+    def restart(self, pid=None):
+        if str(pid) != str(sickbeard.PID):
+            return self.redirect("/home/")
 
-        if show_obj:
-            sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj])  # @UndefinedVariable
+        t = PageTemplate(rh=self, file="restart.tmpl")
+        t.submenu = self.HomeMenu()
 
-        redirect("/manage/backlogOverview/")
+        # restart
+        sickbeard.events.put(sickbeard.events.SystemEvent.RESTART)
 
+        return t.respond()
 
-    def backlogOverview(self, *args, **kwargs):
+    def updateCheck(self, pid=None):
+        if str(pid) != str(sickbeard.PID):
+            return self.redirect('/home/')
 
-        t = PageTemplate(headers=self.request.headers, file="manage_backlogOverview.tmpl")
-        t.submenu = ManageMenu()
+        sickbeard.versionCheckScheduler.action.check_for_new_version(force=True)
 
-        showCounts = {}
-        showCats = {}
-        showSQLResults = {}
+        return self.redirect('/home/')
 
-        myDB = db.DBConnection()
-        for curShow in sickbeard.showList:
+    def update(self, pid=None):
+        if str(pid) != str(sickbeard.PID):
+            return self.redirect('/home/')
 
-            epCounts = {}
-            epCats = {}
-            epCounts[Overview.SKIPPED] = 0
-            epCounts[Overview.WANTED] = 0
-            epCounts[Overview.QUAL] = 0
-            epCounts[Overview.GOOD] = 0
-            epCounts[Overview.UNAIRED] = 0
-            epCounts[Overview.SNATCHED] = 0
+        if sickbeard.versionCheckScheduler.action.update():
+            # do a hard restart
+            sickbeard.events.put(sickbeard.events.SystemEvent.RESTART)
 
-            sqlResults = myDB.select(
-                "SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC",
-                [curShow.indexerid])
+            t = PageTemplate(rh=self, file="restart_bare.tmpl")
+            return t.respond()
+        else:
+            return self._genericMessage("Update Failed",
+                                        "Update wasn't successful, not restarting. Check your log for more information.")
 
-            for curResult in sqlResults:
-                curEpCat = curShow.getOverview(int(curResult["status"]))
-                if curEpCat:
-                    epCats[str(curResult["season"]) + "x" + str(curResult["episode"])] = curEpCat
-                    epCounts[curEpCat] += 1
+    def branchCheckout(self, branch):
+        sickbeard.BRANCH = branch
+        ui.notifications.message('Checking out branch: ', branch)
+        return self.update(sickbeard.PID)
 
-            showCounts[curShow.indexerid] = epCounts
-            showCats[curShow.indexerid] = epCats
-            showSQLResults[curShow.indexerid] = sqlResults
+    def displayShow(self, show=None):
 
-        t.showCounts = showCounts
-        t.showCats = showCats
-        t.showSQLResults = showSQLResults
+        if show is None:
+            return self._genericMessage("Error", "Invalid show ID")
+        else:
+            showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-        return _munge(t)
+            if showObj is None:
+                return self._genericMessage("Error", "Show not in show list")
 
+        myDB = db.DBConnection()
+        seasonResults = myDB.select(
+            "SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season DESC",
+            [showObj.indexerid]
+        )
 
-    def massEdit(self, toEdit=None):
+        sqlResults = myDB.select(
+            "SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC",
+            [showObj.indexerid]
+        )
 
-        t = PageTemplate(headers=self.request.headers, file="manage_massEdit.tmpl")
-        t.submenu = ManageMenu()
+        t = PageTemplate(rh=self, file="displayShow.tmpl")
+        t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}]
 
-        if not toEdit:
-            redirect("/manage/")
+        try:
+            t.showLoc = (showObj.location, True)
+        except sickbeard.exceptions.ShowDirNotFoundException:
+            t.showLoc = (showObj._location, False)
 
-        showIDs = toEdit.split("|")
-        showList = []
-        for curID in showIDs:
-            curID = int(curID)
-            showObj = helpers.findCertainShow(sickbeard.showList, curID)
-            if showObj:
-                showList.append(showObj)
+        show_message = ''
 
-        archive_firstmatch_all_same = True
-        last_archive_firstmatch = None
+        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj):
+            show_message = 'This show is in the process of being downloaded - the info below is incomplete.'
 
-        flatten_folders_all_same = True
-        last_flatten_folders = None
+        elif sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):
+            show_message = 'The information on this page is in the process of being updated.'
 
-        paused_all_same = True
-        last_paused = None
+        elif sickbeard.showQueueScheduler.action.isBeingRefreshed(showObj):
+            show_message = 'The episodes below are currently being refreshed from disk'
 
-        anime_all_same = True
-        last_anime = None
+        elif sickbeard.showQueueScheduler.action.isBeingSubtitled(showObj):
+            show_message = 'Currently downloading subtitles for this show'
 
-        sports_all_same = True
-        last_sports = None
+        elif sickbeard.showQueueScheduler.action.isInRefreshQueue(showObj):
+            show_message = 'This show is queued to be refreshed.'
 
-        quality_all_same = True
-        last_quality = None
+        elif sickbeard.showQueueScheduler.action.isInUpdateQueue(showObj):
+            show_message = 'This show is queued and awaiting an update.'
 
-        subtitles_all_same = True
-        last_subtitles = None
+        elif sickbeard.showQueueScheduler.action.isInSubtitleQueue(showObj):
+            show_message = 'This show is queued and awaiting subtitles download.'
 
-        scene_all_same = True
-        last_scene = None
+        if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj):
+            if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):
+                t.submenu.append(
+                    {'title': 'Remove', 'path': 'home/deleteShow?show=%d' % showObj.indexerid, 'confirm': True})
+                t.submenu.append({'title': 'Re-scan files', 'path': 'home/refreshShow?show=%d' % showObj.indexerid})
+                t.submenu.append(
+                    {'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&amp;force=1' % showObj.indexerid})
+                t.submenu.append({'title': 'Update show in KODI',
+                                  'path': 'home/updateKODI?showName=%s' % urllib.quote_plus(
+                                      showObj.name.encode('utf-8')), 'requires': self.haveKODI})
+                t.submenu.append({'title': 'Preview Rename', 'path': 'home/testRename?show=%d' % showObj.indexerid})
+                if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled(
+                        showObj) and showObj.subtitles:
+                    t.submenu.append(
+                        {'title': 'Download Subtitles', 'path': 'home/subtitleShow?show=%d' % showObj.indexerid})
 
-        air_by_date_all_same = True
-        last_air_by_date = None
+        t.show = showObj
+        t.sqlResults = sqlResults
+        t.seasonResults = seasonResults
+        t.show_message = show_message
 
-        root_dir_list = []
+        epCounts = {}
+        epCats = {}
+        epCounts[Overview.SKIPPED] = 0
+        epCounts[Overview.WANTED] = 0
+        epCounts[Overview.QUAL] = 0
+        epCounts[Overview.GOOD] = 0
+        epCounts[Overview.UNAIRED] = 0
+        epCounts[Overview.SNATCHED] = 0
 
-        for curShow in showList:
+        for curResult in sqlResults:
+            curEpCat = showObj.getOverview(int(curResult["status"] or -1))
+            if curEpCat:
+                epCats[str(curResult["season"]) + "x" + str(curResult["episode"])] = curEpCat
+                epCounts[curEpCat] += 1
 
-            cur_root_dir = ek.ek(os.path.dirname, curShow._location)
-            if cur_root_dir not in root_dir_list:
-                root_dir_list.append(cur_root_dir)
+        def titler(x):
+            return (helpers.remove_article(x), x)[not x or sickbeard.SORT_ARTICLE]
 
-            if archive_firstmatch_all_same:
-                # if we had a value already and this value is different then they're not all the same
-                if last_archive_firstmatch not in (None, curShow.archive_firstmatch):
-                    archive_firstmatch_all_same = False
+        if sickbeard.ANIME_SPLIT_HOME:
+            shows = []
+            anime = []
+            for show in sickbeard.showList:
+                if show.is_anime:
+                    anime.append(show)
                 else:
-                    last_archive_firstmatch = curShow.archive_firstmatch
+                    shows.append(show)
+            t.sortedShowLists = [["Shows", sorted(shows, lambda x, y: cmp(titler(x.name), titler(y.name)))],
+                                 ["Anime", sorted(anime, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
+        else:
+            t.sortedShowLists = [
+                ["Shows", sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
 
-            # if we know they're not all the same then no point even bothering
-            if paused_all_same:
-                # if we had a value already and this value is different then they're not all the same
-                if last_paused not in (None, curShow.paused):
-                    paused_all_same = False
-                else:
-                    last_paused = curShow.paused
+        t.bwl = None
+        if showObj.is_anime:
+            t.bwl = BlackAndWhiteList(showObj.indexerid)
 
-            if anime_all_same:
-                # if we had a value already and this value is different then they're not all the same
-                if last_anime not in (None, curShow.is_anime):
-                    anime_all_same = False
-                else:
-                    last_anime = curShow.anime
+        t.epCounts = epCounts
+        t.epCats = epCats
 
-            if flatten_folders_all_same:
-                if last_flatten_folders not in (None, curShow.flatten_folders):
-                    flatten_folders_all_same = False
-                else:
-                    last_flatten_folders = curShow.flatten_folders
+        showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid)
 
-            if quality_all_same:
-                if last_quality not in (None, curShow.quality):
-                    quality_all_same = False
-                else:
-                    last_quality = curShow.quality
+        indexerid = int(showObj.indexerid)
+        indexer = int(showObj.indexer)
+        t.all_scene_exceptions = showObj.exceptions
+        t.scene_numbering = get_scene_numbering_for_show(indexerid, indexer)
+        t.xem_numbering = get_xem_numbering_for_show(indexerid, indexer)
+        t.scene_absolute_numbering = get_scene_absolute_numbering_for_show(indexerid, indexer)
+        t.xem_absolute_numbering = get_xem_absolute_numbering_for_show(indexerid, indexer)
 
-            if subtitles_all_same:
-                if last_subtitles not in (None, curShow.subtitles):
-                    subtitles_all_same = False
-                else:
-                    last_subtitles = curShow.subtitles
+        return t.respond()
 
-            if scene_all_same:
-                if last_scene not in (None, curShow.scene):
-                    scene_all_same = False
-                else:
-                    last_scene = curShow.scene
 
-            if sports_all_same:
-                if last_sports not in (None, curShow.sports):
-                    sports_all_same = False
-                else:
-                    last_sports = curShow.sports
+    def plotDetails(self, show, season, episode):
+        myDB = db.DBConnection()
+        result = myDB.selectOne(
+            "SELECT description FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?",
+            (int(show), int(season), int(episode)))
+        return result['description'] if result else 'Episode not found.'
 
-            if air_by_date_all_same:
-                if last_air_by_date not in (None, curShow.air_by_date):
-                    air_by_date_all_same = False
-                else:
-                    last_air_by_date = curShow.air_by_date
 
-        t.showList = toEdit
-        t.archive_firstmatch_value = last_archive_firstmatch if archive_firstmatch_all_same else None
-        t.paused_value = last_paused if paused_all_same else None
-        t.anime_value = last_anime if anime_all_same else None
-        t.flatten_folders_value = last_flatten_folders if flatten_folders_all_same else None
-        t.quality_value = last_quality if quality_all_same else None
-        t.subtitles_value = last_subtitles if subtitles_all_same else None
-        t.scene_value = last_scene if scene_all_same else None
-        t.sports_value = last_sports if sports_all_same else None
-        t.air_by_date_value = last_air_by_date if air_by_date_all_same else None
-        t.root_dir_list = root_dir_list
+    def sceneExceptions(self, show):
+        exceptionsList = sickbeard.scene_exceptions.get_all_scene_exceptions(show)
+        if not exceptionsList:
+            return "No scene exceptions"
 
-        return _munge(t)
+        out = []
+        for season, names in iter(sorted(exceptionsList.iteritems())):
+            if season == -1:
+                season = "*"
+            out.append("S" + str(season) + ": " + ", ".join(names))
+        return "<br/>".join(out)
 
 
-    def massEditSubmit(self, archive_firstmatch=None, paused=None, anime=None, sports=None, scene=None,
-                       flatten_folders=None,
-                       quality_preset=False,
-                       subtitles=None, air_by_date=None, anyQualities=[], bestQualities=[], toEdit=None, *args,
-                       **kwargs):
+    def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[],
+                 flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None,
+                 indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None,
+                 rls_require_words=None, anime=None, blackWords=None, whiteWords=None, blacklist=None, whitelist=None,
+                 scene=None, defaultEpStatus=None):
 
-        dir_map = {}
-        for cur_arg in kwargs:
-            if not cur_arg.startswith('orig_root_dir_'):
-                continue
-            which_index = cur_arg.replace('orig_root_dir_', '')
-            end_dir = kwargs['new_root_dir_' + which_index]
-            dir_map[kwargs[cur_arg]] = end_dir
+        if show is None:
+            errString = "Invalid show ID: " + str(show)
+            if directCall:
+                return [errString]
+            else:
+                return self._genericMessage("Error", errString)
 
-        showIDs = toEdit.split("|")
-        errors = []
-        for curShow in showIDs:
-            curErrors = []
-            showObj = helpers.findCertainShow(sickbeard.showList, int(curShow))
-            if not showObj:
-                continue
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-            cur_root_dir = ek.ek(os.path.dirname, showObj._location)
-            cur_show_dir = ek.ek(os.path.basename, showObj._location)
-            if cur_root_dir in dir_map and cur_root_dir != dir_map[cur_root_dir]:
-                new_show_dir = ek.ek(os.path.join, dir_map[cur_root_dir], cur_show_dir)
-                logger.log(
-                    u"For show " + showObj.name + " changing dir from " + showObj._location + " to " + new_show_dir)
+        if not showObj:
+            errString = "Unable to find the specified show: " + str(show)
+            if directCall:
+                return [errString]
             else:
-                new_show_dir = showObj._location
-
-            if archive_firstmatch == 'keep':
-                new_archive_firstmatch = showObj.archive_firstmatch
-            else:
-                new_archive_firstmatch = True if archive_firstmatch == 'enable' else False
-            new_archive_firstmatch = 'on' if new_archive_firstmatch else 'off'
-
-            if paused == 'keep':
-                new_paused = showObj.paused
-            else:
-                new_paused = True if paused == 'enable' else False
-            new_paused = 'on' if new_paused else 'off'
-
-            if anime == 'keep':
-                new_anime = showObj.anime
-            else:
-                new_anime = True if anime == 'enable' else False
-            new_anime = 'on' if new_anime else 'off'
-
-            if sports == 'keep':
-                new_sports = showObj.sports
-            else:
-                new_sports = True if sports == 'enable' else False
-            new_sports = 'on' if new_sports else 'off'
-
-            if scene == 'keep':
-                new_scene = showObj.is_scene
-            else:
-                new_scene = True if scene == 'enable' else False
-            new_scene = 'on' if new_scene else 'off'
-
-            if air_by_date == 'keep':
-                new_air_by_date = showObj.air_by_date
-            else:
-                new_air_by_date = True if air_by_date == 'enable' else False
-            new_air_by_date = 'on' if new_air_by_date else 'off'
+                return self._genericMessage("Error", errString)
 
-            if flatten_folders == 'keep':
-                new_flatten_folders = showObj.flatten_folders
-            else:
-                new_flatten_folders = True if flatten_folders == 'enable' else False
-            new_flatten_folders = 'on' if new_flatten_folders else 'off'
+        showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid)
 
-            if subtitles == 'keep':
-                new_subtitles = showObj.subtitles
-            else:
-                new_subtitles = True if subtitles == 'enable' else False
+        if not location and not anyQualities and not bestQualities and not flatten_folders:
+            t = PageTemplate(rh=self, file="editShow.tmpl")
+            t.submenu = self.HomeMenu()
 
-            new_subtitles = 'on' if new_subtitles else 'off'
+            if showObj.is_anime:
+                bwl = BlackAndWhiteList(showObj.indexerid)
 
-            if quality_preset == 'keep':
-                anyQualities, bestQualities = Quality.splitQuality(showObj.quality)
+                t.whiteWords = ""
+                if "global" in bwl.whiteDict:
+                    t.whiteWords = ", ".join(bwl.whiteDict["global"])
 
-            exceptions_list = []
+                t.blackWords = ""
+                if "global" in bwl.blackDict:
+                    t.blackWords = ", ".join(bwl.blackDict["global"])
 
-            curErrors += Home(self.application, self.request).editShow(curShow, new_show_dir, anyQualities,
-                                                                       bestQualities, exceptions_list,
-                                                                       archive_firstmatch=new_archive_firstmatch,
-                                                                       flatten_folders=new_flatten_folders,
-                                                                       paused=new_paused, sports=new_sports,
-                                                                       subtitles=new_subtitles, anime=new_anime,
-                                                                       scene=new_scene, air_by_date=new_air_by_date,
-                                                                       directCall=True)
+                t.whitelist = []
+                if bwl.whiteDict.has_key("release_group"):
+                    t.whitelist = bwl.whiteDict["release_group"]
 
-            if curErrors:
-                logger.log(u"Errors: " + str(curErrors), logger.ERROR)
-                errors.append('<b>%s:</b>\n<ul>' % showObj.name + ' '.join(
-                    ['<li>%s</li>' % error for error in curErrors]) + "</ul>")
+                t.blacklist = []
+                if bwl.blackDict.has_key("release_group"):
+                    t.blacklist = bwl.blackDict["release_group"]
 
-        if len(errors) > 0:
-            ui.notifications.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"),
-                                   " ".join(errors))
+                t.groups = []
+                if helpers.set_up_anidb_connection():
+                    anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=showObj.name)
+                    t.groups = anime.get_groups()
 
-        redirect("/manage/")
+            with showObj.lock:
+                t.show = showObj
+                t.scene_exceptions = get_scene_exceptions(showObj.indexerid)
 
+            return t.respond()
 
-    def massUpdate(self, toUpdate=None, toRefresh=None, toRename=None, toDelete=None, toRemove=None, toMetadata=None,
-                   toSubtitle=None):
+        flatten_folders = config.checkbox_to_value(flatten_folders)
+        dvdorder = config.checkbox_to_value(dvdorder)
+        archive_firstmatch = config.checkbox_to_value(archive_firstmatch)
+        paused = config.checkbox_to_value(paused)
+        air_by_date = config.checkbox_to_value(air_by_date)
+        scene = config.checkbox_to_value(scene)
+        sports = config.checkbox_to_value(sports)
+        anime = config.checkbox_to_value(anime)
+        subtitles = config.checkbox_to_value(subtitles)
 
-        if toUpdate is not None:
-            toUpdate = toUpdate.split('|')
+        if indexerLang and indexerLang in sickbeard.indexerApi(showObj.indexer).indexer().config['valid_languages']:
+            indexer_lang = indexerLang
         else:
-            toUpdate = []
+            indexer_lang = showObj.lang
 
-        if toRefresh is not None:
-            toRefresh = toRefresh.split('|')
+        # if we changed the language then kick off an update
+        if indexer_lang == showObj.lang:
+            do_update = False
         else:
-            toRefresh = []
+            do_update = True
 
-        if toRename is not None:
-            toRename = toRename.split('|')
+        if scene == showObj.scene and anime == showObj.anime:
+            do_update_scene_numbering = False
         else:
-            toRename = []
+            do_update_scene_numbering = True
 
-        if toSubtitle is not None:
-            toSubtitle = toSubtitle.split('|')
-        else:
-            toSubtitle = []
+        if type(anyQualities) != list:
+            anyQualities = [anyQualities]
 
-        if toDelete is not None:
-            toDelete = toDelete.split('|')
-        else:
-            toDelete = []
+        if type(bestQualities) != list:
+            bestQualities = [bestQualities]
 
-        if toRemove is not None:
-            toRemove = toRemove.split('|')
-        else:
-            toRemove = []
+        if type(exceptions_list) != list:
+            exceptions_list = [exceptions_list]
 
-        if toMetadata is not None:
-            toMetadata = toMetadata.split('|')
+        # If directCall from mass_edit_update no scene exceptions handling or blackandwhite list handling
+        if directCall:
+            do_update_exceptions = False
         else:
-            toMetadata = []
-
-        errors = []
-        refreshes = []
-        updates = []
-        renames = []
-        subtitles = []
-
-        for curShowID in set(toUpdate + toRefresh + toRename + toSubtitle + toDelete + toRemove + toMetadata):
-
-            if curShowID == '':
-                continue
+            if set(exceptions_list) == set(showObj.exceptions):
+                do_update_exceptions = False
+            else:
+                do_update_exceptions = True
 
-            showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(curShowID))
+            if showObj.is_anime:
+                bwl = BlackAndWhiteList(showObj.indexerid)
+                if whitelist:
+                    whitelist = whitelist.split(",")
+                    shortWhiteList = []
+                    if helpers.set_up_anidb_connection():
+                        for groupName in whitelist:
+                            group = sickbeard.ADBA_CONNECTION.group(gname=groupName)
+                            for line in group.datalines:
+                                if line["shortname"]:
+                                    shortWhiteList.append(line["shortname"])
+                            else:
+                                if not groupName in shortWhiteList:
+                                    shortWhiteList.append(groupName)
+                    else:
+                        shortWhiteList = whitelist
+                    bwl.set_white_keywords_for("release_group", shortWhiteList)
+                else:
+                    bwl.set_white_keywords_for("release_group", [])
 
-            if showObj is None:
-                continue
+                if blacklist:
+                    blacklist = blacklist.split(",")
+                    shortBlacklist = []
+                    if helpers.set_up_anidb_connection():
+                        for groupName in blacklist:
+                            group = sickbeard.ADBA_CONNECTION.group(gname=groupName)
+                            for line in group.datalines:
+                                if line["shortname"]:
+                                    shortBlacklist.append(line["shortname"])
+                            else:
+                                if not groupName in shortBlacklist:
+                                    shortBlacklist.append(groupName)
+                    else:
+                        shortBlacklist = blacklist
+                    bwl.set_black_keywords_for("release_group", shortBlacklist)
+                else:
+                    bwl.set_black_keywords_for("release_group", [])
 
-            if curShowID in toDelete:
-                showObj.deleteShow(True)
-                # don't do anything else if it's being deleted
-                continue
+                if whiteWords:
+                    whiteWords = [x.strip() for x in whiteWords.split(",")]
+                    bwl.set_white_keywords_for("global", whiteWords)
+                else:
+                    bwl.set_white_keywords_for("global", [])
 
-            if curShowID in toRemove:
-                showObj.deleteShow()
-                # don't do anything else if it's being remove
-                continue
+                if blackWords:
+                    blackWords = [x.strip() for x in blackWords.split(",")]
+                    bwl.set_black_keywords_for("global", blackWords)
+                else:
+                    bwl.set_black_keywords_for("global", [])
 
-            if curShowID in toUpdate:
-                try:
-                    sickbeard.showQueueScheduler.action.updateShow(showObj, True)  # @UndefinedVariable
-                    updates.append(showObj.name)
-                except exceptions.CantUpdateException, e:
-                    errors.append("Unable to update show " + showObj.name + ": " + ex(e))
+        errors = []
+        with showObj.lock:
+            newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
+            showObj.quality = newQuality
+            showObj.archive_firstmatch = archive_firstmatch
 
-            # don't bother refreshing shows that were updated anyway
-            if curShowID in toRefresh and curShowID not in toUpdate:
+            # reversed for now
+            if bool(showObj.flatten_folders) != bool(flatten_folders):
+                showObj.flatten_folders = flatten_folders
                 try:
-                    sickbeard.showQueueScheduler.action.refreshShow(showObj)  # @UndefinedVariable
-                    refreshes.append(showObj.name)
+                    sickbeard.showQueueScheduler.action.refreshShow(showObj)
                 except exceptions.CantRefreshException, e:
-                    errors.append("Unable to refresh show " + showObj.name + ": " + ex(e))
-
-            if curShowID in toRename:
-                sickbeard.showQueueScheduler.action.renameShowEpisodes(showObj)  # @UndefinedVariable
-                renames.append(showObj.name)
-
-            if curShowID in toSubtitle:
-                sickbeard.showQueueScheduler.action.downloadSubtitles(showObj)  # @UndefinedVariable
-                subtitles.append(showObj.name)
+                    errors.append("Unable to refresh this show: " + ex(e))
 
-        if len(errors) > 0:
-            ui.notifications.error("Errors encountered",
-                                   '<br >\n'.join(errors))
+            showObj.paused = paused
+            showObj.scene = scene
+            showObj.anime = anime
+            showObj.sports = sports
+            showObj.subtitles = subtitles
+            showObj.air_by_date = air_by_date
 
-        messageDetail = ""
+            if not directCall:
+                showObj.lang = indexer_lang
+                showObj.dvdorder = dvdorder
+                showObj.rls_ignore_words = rls_ignore_words.strip()
+                showObj.rls_require_words = rls_require_words.strip()
+                showObj.default_ep_status = defaultEpStatus
 
-        if len(updates) > 0:
-            messageDetail += "<br /><b>Updates</b><br /><ul><li>"
-            messageDetail += "</li><li>".join(updates)
-            messageDetail += "</li></ul>"
+            # if we change location clear the db of episodes, change it, write to db, and rescan
+            if os.path.normpath(showObj._location) != os.path.normpath(location):
+                logger.log(os.path.normpath(showObj._location) + " != " + os.path.normpath(location), logger.DEBUG)
+                if not ek.ek(os.path.isdir, location) and not sickbeard.CREATE_MISSING_SHOW_DIRS:
+                    errors.append("New location <tt>%s</tt> does not exist" % location)
 
-        if len(refreshes) > 0:
-            messageDetail += "<br /><b>Refreshes</b><br /><ul><li>"
-            messageDetail += "</li><li>".join(refreshes)
-            messageDetail += "</li></ul>"
-
-        if len(renames) > 0:
-            messageDetail += "<br /><b>Renames</b><br /><ul><li>"
-            messageDetail += "</li><li>".join(renames)
-            messageDetail += "</li></ul>"
-
-        if len(subtitles) > 0:
-            messageDetail += "<br /><b>Subtitles</b><br /><ul><li>"
-            messageDetail += "</li><li>".join(subtitles)
-            messageDetail += "</li></ul>"
-
-        if len(updates + refreshes + renames + subtitles) > 0:
-            ui.notifications.message("The following actions were queued:",
-                                     messageDetail)
-
-        redirect("/manage/")
-
-
-    def manageTorrents(self, *args, **kwargs):
-
-        t = PageTemplate(headers=self.request.headers, file="manage_torrents.tmpl")
-        t.info_download_station = ''
-        t.submenu = ManageMenu()
-
-        if re.search('localhost', sickbeard.TORRENT_HOST):
+                # don't bother if we're going to update anyway
+                elif not do_update:
+                    # change it
+                    try:
+                        showObj.location = location
+                        try:
+                            sickbeard.showQueueScheduler.action.refreshShow(showObj)
+                        except exceptions.CantRefreshException, e:
+                            errors.append("Unable to refresh this show:" + ex(e))
+                            # grab updated info from TVDB
+                            # showObj.loadEpisodesFromIndexer()
+                            # rescan the episodes in the new folder
+                    except exceptions.NoNFOException:
+                        errors.append(
+                            "The folder at <tt>%s</tt> doesn't contain a tvshow.nfo - copy your files to that folder before you change the directory in SickRage." % location)
 
-            if sickbeard.LOCALHOST_IP == '':
-                t.webui_url = re.sub('localhost', helpers.get_lan_ip(), sickbeard.TORRENT_HOST)
-            else:
-                t.webui_url = re.sub('localhost', sickbeard.LOCALHOST_IP, sickbeard.TORRENT_HOST)
-        else:
-            t.webui_url = sickbeard.TORRENT_HOST
+            # save it to the DB
+            showObj.saveToDB()
 
-        if sickbeard.TORRENT_METHOD == 'utorrent':
-            t.webui_url = '/'.join(s.strip('/') for s in (t.webui_url, 'gui/'))
-        if sickbeard.TORRENT_METHOD == 'download_station':
-            if helpers.check_url(t.webui_url + 'download/'):
-                t.webui_url = t.webui_url + 'download/'
-            else:
-                t.info_download_station = '<p>To have a better experience please set the Download Station alias as <code>download</code>, you can check this setting in the Synology DSM <b>Control Panel</b> > <b>Application Portal</b>. Make sure you allow DSM to be embedded with iFrames too in <b>Control Panel</b> > <b>DSM Settings</b> > <b>Security</b>.</p><br/><p>There is more information about this available <a href="https://github.com/midgetspy/Sick-Beard/pull/338">here</a>.</p><br/>'
+        # force the update
+        if do_update:
+            try:
+                sickbeard.showQueueScheduler.action.updateShow(showObj, True)
+                time.sleep(cpu_presets[sickbeard.CPU_PRESET])
+            except exceptions.CantUpdateException, e:
+                errors.append("Unable to force an update on the show.")
 
-        return _munge(t)
+        if do_update_exceptions:
+            try:
+                scene_exceptions.update_scene_exceptions(showObj.indexerid, exceptions_list)  # @UndefinedVdexerid)
+                time.sleep(cpu_presets[sickbeard.CPU_PRESET])
+            except exceptions.CantUpdateException, e:
+                errors.append("Unable to force an update on scene exceptions of the show.")
 
+        if do_update_scene_numbering:
+            try:
+                sickbeard.scene_numbering.xem_refresh(showObj.indexerid, showObj.indexer)
+                time.sleep(cpu_presets[sickbeard.CPU_PRESET])
+            except exceptions.CantUpdateException, e:
+                errors.append("Unable to force an update on scene numbering of the show.")
 
-    def failedDownloads(self, limit=100, toRemove=None):
+        if directCall:
+            return errors
 
-        myDB = db.DBConnection('failed.db')
+        if len(errors) > 0:
+            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>")
 
-        if limit == "0":
-            sqlResults = myDB.select("SELECT * FROM failed")
-        else:
-            sqlResults = myDB.select("SELECT * FROM failed LIMIT ?", [limit])
+        return self.redirect("/home/displayShow?show=" + show)
 
-        toRemove = toRemove.split("|") if toRemove is not None else []
 
-        for release in toRemove:
-            myDB.action('DELETE FROM failed WHERE release = ?', [release])
+    def deleteShow(self, show=None, full=0):
 
-        if toRemove:
-            redirect('/manage/failedDownloads/')
+        if show is None:
+            return self._genericMessage("Error", "Invalid show ID")
 
-        t = PageTemplate(headers=self.request.headers, file="manage_failedDownloads.tmpl")
-        t.failedResults = sqlResults
-        t.limit = limit
-        t.submenu = ManageMenu()
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-        return _munge(t)
+        if showObj is None:
+            return self._genericMessage("Error", "Unable to find the specified show")
 
+        if sickbeard.showQueueScheduler.action.isBeingAdded(
+                showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):
+            return self._genericMessage("Error", "Shows can't be deleted while they're being added or updated.")
 
-class History(MainHandler):
-    def index(self, limit=100):
+        if sickbeard.USE_TRAKT and sickbeard.TRAKT_SYNC:
+            # remove show from trakt.tv library
+            sickbeard.traktCheckerScheduler.action.removeShowFromTraktLibrary(showObj)
 
-        # sqlResults = myDB.select("SELECT h.*, show_name, name FROM history h, tv_shows s, tv_episodes e WHERE h.showid=s.indexer_id AND h.showid=e.showid AND h.season=e.season AND h.episode=e.episode ORDER BY date DESC LIMIT "+str(numPerPage*(p-1))+", "+str(numPerPage))
-        myDB = db.DBConnection()
-        if limit == "0":
-            sqlResults = myDB.select(
-                "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC")
-        else:
-            sqlResults = myDB.select(
-                "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC LIMIT ?",
-                [limit])
+        showObj.deleteShow(bool(full))
 
-        history = {'show_id': 0, 'season': 0, 'episode': 0, 'quality': 0,
-                   'actions': [{'time': '', 'action': '', 'provider': ''}]}
-        compact = []
+        ui.notifications.message('<b>%s</b> has been %s %s' %
+                                 (showObj.name,
+                                  ('deleted', 'trashed')[sickbeard.TRASH_REMOVE_SHOW],
+                                  ('(media untouched)', '(with all related media)')[bool(full)]))
+        return self.redirect("/home/")
 
-        for sql_result in sqlResults:
 
-            if not any((history['show_id'] == sql_result['showid']
-                        and history['season'] == sql_result['season']
-                        and history['episode'] == sql_result['episode']
-                        and history['quality'] == sql_result['quality'])
-                       for history in compact):
+    def refreshShow(self, show=None):
 
-                history = {}
-                history['show_id'] = sql_result['showid']
-                history['season'] = sql_result['season']
-                history['episode'] = sql_result['episode']
-                history['quality'] = sql_result['quality']
-                history['show_name'] = sql_result['show_name']
-                history['resource'] = sql_result['resource']
+        if show is None:
+            return self._genericMessage("Error", "Invalid show ID")
 
-                action = {}
-                history['actions'] = []
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-                action['time'] = sql_result['date']
-                action['action'] = sql_result['action']
-                action['provider'] = sql_result['provider']
-                action['resource'] = sql_result['resource']
-                history['actions'].append(action)
-                history['actions'].sort(key=lambda x: x['time'])
-                compact.append(history)
-            else:
-                index = [i for i, dict in enumerate(compact) \
-                         if dict['show_id'] == sql_result['showid'] \
-                         and dict['season'] == sql_result['season'] \
-                         and dict['episode'] == sql_result['episode']
-                         and dict['quality'] == sql_result['quality']][0]
+        if showObj is None:
+            return self._genericMessage("Error", "Unable to find the specified show")
 
-                action = {}
-                history = compact[index]
+        # force the update from the DB
+        try:
+            sickbeard.showQueueScheduler.action.refreshShow(showObj)
+        except exceptions.CantRefreshException, e:
+            ui.notifications.error("Unable to refresh this show.",
+                                   ex(e))
 
-                action['time'] = sql_result['date']
-                action['action'] = sql_result['action']
-                action['provider'] = sql_result['provider']
-                action['resource'] = sql_result['resource']
-                history['actions'].append(action)
-                history['actions'].sort(key=lambda x: x['time'], reverse=True)
+        time.sleep(cpu_presets[sickbeard.CPU_PRESET])
 
-        t = PageTemplate(headers=self.request.headers, file="history.tmpl")
-        t.historyResults = sqlResults
-        t.compactResults = compact
-        t.limit = limit
-        t.submenu = [
-            {'title': 'Clear History', 'path': 'history/clearHistory'},
-            {'title': 'Trim History', 'path': 'history/trimHistory'},
-        ]
+        return self.redirect("/home/displayShow?show=" + str(showObj.indexerid))
 
-        return _munge(t)
 
+    def updateShow(self, show=None, force=0):
 
-    def clearHistory(self, *args, **kwargs):
+        if show is None:
+            return self._genericMessage("Error", "Invalid show ID")
 
-        myDB = db.DBConnection()
-        myDB.action("DELETE FROM history WHERE 1=1")
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-        ui.notifications.message('History cleared')
-        redirect("/history/")
+        if showObj is None:
+            return self._genericMessage("Error", "Unable to find the specified show")
 
+        # force the update
+        try:
+            sickbeard.showQueueScheduler.action.updateShow(showObj, bool(force))
+        except exceptions.CantUpdateException, e:
+            ui.notifications.error("Unable to update this show.", ex(e))
 
-    def trimHistory(self, *args, **kwargs):
+        # just give it some time
+        time.sleep(cpu_presets[sickbeard.CPU_PRESET])
 
-        myDB = db.DBConnection()
-        myDB.action("DELETE FROM history WHERE date < " + str(
-            (datetime.datetime.today() - datetime.timedelta(days=30)).strftime(history.dateFormat)))
+        return self.redirect("/home/displayShow?show=" + str(showObj.indexerid))
 
-        ui.notifications.message('Removed history entries greater than 30 days old')
-        redirect("/history/")
+    def subtitleShow(self, show=None, force=0):
 
+        if show is None:
+            return self._genericMessage("Error", "Invalid show ID")
 
-ConfigMenu = [
-    {'title': 'General', 'path': 'config/general/'},
-    {'title': 'Backup/Restore', 'path': 'config/backuprestore/'},
-    {'title': 'Search Settings', 'path': 'config/search/'},
-    {'title': 'Search Providers', 'path': 'config/providers/'},
-    {'title': 'Subtitles Settings', 'path': 'config/subtitles/'},
-    {'title': 'Post Processing', 'path': 'config/postProcessing/'},
-    {'title': 'Notifications', 'path': 'config/notifications/'},
-    {'title': 'Anime', 'path': 'config/anime/'},
-]
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
+        if showObj is None:
+            return self._genericMessage("Error", "Unable to find the specified show")
 
-class ConfigGeneral(MainHandler):
-    def index(self, *args, **kwargs):
+        # search and download subtitles
+        sickbeard.showQueueScheduler.action.downloadSubtitles(showObj, bool(force))
 
-        t = PageTemplate(headers=self.request.headers, file="config_general.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+        time.sleep(cpu_presets[sickbeard.CPU_PRESET])
 
+        return self.redirect("/home/displayShow?show=" + str(showObj.indexerid))
 
-    def saveRootDirs(self, rootDirString=None):
-        sickbeard.ROOT_DIRS = rootDirString
 
-    def saveAddShowDefaults(self, defaultStatus, anyQualities, bestQualities, defaultFlattenFolders, subtitles=False,
-                            anime=False, scene=False):
+    def updateKODI(self, showName=None):
 
-        if anyQualities:
-            anyQualities = anyQualities.split(',')
+        # only send update to first host in the list -- workaround for kodi sql backend users
+        if sickbeard.KODI_UPDATE_ONLYFIRST:
+            # only send update to first host in the list -- workaround for kodi sql backend users
+            host = sickbeard.KODI_HOST.split(",")[0].strip()
         else:
-            anyQualities = []
+            host = sickbeard.KODI_HOST
 
-        if bestQualities:
-            bestQualities = bestQualities.split(',')
+        if notifiers.kodi_notifier.update_library(showName=showName):
+            ui.notifications.message("Library update command sent to KODI host(s): " + host)
         else:
-            bestQualities = []
+            ui.notifications.error("Unable to contact one or more KODI host(s): " + host)
+        return self.redirect('/home/')
 
-        newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
 
-        sickbeard.STATUS_DEFAULT = int(defaultStatus)
-        sickbeard.QUALITY_DEFAULT = int(newQuality)
+    def updatePLEX(self):
+        if notifiers.plex_notifier.update_library():
+            ui.notifications.message(
+                "Library update command sent to Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST)
+        else:
+            ui.notifications.error("Unable to contact Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST)
+        return self.redirect('/home/')
 
-        sickbeard.FLATTEN_FOLDERS_DEFAULT = config.checkbox_to_value(defaultFlattenFolders)
-        sickbeard.SUBTITLES_DEFAULT = config.checkbox_to_value(subtitles)
+    def setStatus(self, show=None, eps=None, status=None, direct=False):
 
-        sickbeard.ANIME_DEFAULT = config.checkbox_to_value(anime)
-        sickbeard.SCENE_DEFAULT = config.checkbox_to_value(scene)
+        if show is None or eps is None or status is None:
+            errMsg = "You must specify a show and at least one episode"
+            if direct:
+                ui.notifications.error('Error', errMsg)
+                return json.dumps({'result': 'error'})
+            else:
+                return self._genericMessage("Error", errMsg)
 
-        sickbeard.save_config()
+        if not statusStrings.has_key(int(status)):
+            errMsg = "Invalid status"
+            if direct:
+                ui.notifications.error('Error', errMsg)
+                return json.dumps({'result': 'error'})
+            else:
+                return self._genericMessage("Error", errMsg)
 
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-    def generateKey(self, *args, **kwargs):
-        """ Return a new randomized API_KEY
-        """
+        if showObj is None:
+            errMsg = "Error", "Show not in show list"
+            if direct:
+                ui.notifications.error('Error', errMsg)
+                return json.dumps({'result': 'error'})
+            else:
+                return self._genericMessage("Error", errMsg)
 
-        try:
-            from hashlib import md5
-        except ImportError:
-            from md5 import md5
+        segments = {}
+        if eps is not None:
 
-        # Create some values to seed md5
-        t = str(time.time())
-        r = str(random.random())
+            sql_l = []
+            for curEp in eps.split('|'):
 
-        # Create the md5 instance and give it the current time
-        m = md5(t)
+                logger.log(u"Attempting to set status on episode " + curEp + " to " + status, logger.DEBUG)
 
-        # Update the md5 instance with the random variable
-        m.update(r)
+                epInfo = curEp.split('x')
 
-        # Return a hex digest of the md5, eg 49f68a5c8493ec2c0bf489821c21fc3b
-        logger.log(u"New API generated")
-        return m.hexdigest()
+                epObj = showObj.getEpisode(int(epInfo[0]), int(epInfo[1]))
 
+                if epObj is None:
+                    return self._genericMessage("Error", "Episode couldn't be retrieved")
 
-    def saveGeneral(self, log_dir=None, web_port=None, web_log=None, encryption_version=None, web_ipv6=None,
-                    update_shows_on_start=None, trash_remove_show=None, trash_rotate_logs=None, update_frequency=None,
-                    launch_browser=None, web_username=None,
-                    use_api=None, api_key=None, indexer_default=None, timezone_display=None, cpu_preset=None,
-                    web_password=None, version_notify=None, enable_https=None, https_cert=None, https_key=None,
-                    handle_reverse_proxy=None, sort_article=None, auto_update=None, notify_on_update=None,
-                    proxy_setting=None, proxy_indexers=None, anon_redirect=None, git_path=None, git_remote=None,
-                    calendar_unprotected=None,
-                    fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None,
-                    indexer_timeout=None, play_videos=None, rootDir=None, theme_name=None):
-
-        results = []
-
-        # Misc
-        sickbeard.PLAY_VIDEOS = config.checkbox_to_value(play_videos)
-        sickbeard.LAUNCH_BROWSER = config.checkbox_to_value(launch_browser)
-        config.change_VERSION_NOTIFY(config.checkbox_to_value(version_notify))
-        sickbeard.AUTO_UPDATE = config.checkbox_to_value(auto_update)
-        sickbeard.NOTIFY_ON_UPDATE = config.checkbox_to_value(notify_on_update)
-        # sickbeard.LOG_DIR is set in config.change_LOG_DIR()
+                if int(status) in [WANTED, FAILED]:
+                    # figure out what episodes are wanted so we can backlog them
+                    if epObj.season in segments:
+                        segments[epObj.season].append(epObj)
+                    else:
+                        segments[epObj.season] = [epObj]
 
-        sickbeard.UPDATE_SHOWS_ON_START = config.checkbox_to_value(update_shows_on_start)
-        sickbeard.TRASH_REMOVE_SHOW = config.checkbox_to_value(trash_remove_show)
-        sickbeard.TRASH_ROTATE_LOGS = config.checkbox_to_value(trash_rotate_logs)
-        config.change_UPDATE_FREQUENCY(update_frequency)
-        sickbeard.LAUNCH_BROWSER = config.checkbox_to_value(launch_browser)
-        sickbeard.SORT_ARTICLE = config.checkbox_to_value(sort_article)
-        sickbeard.CPU_PRESET = cpu_preset
-        sickbeard.ANON_REDIRECT = anon_redirect
-        sickbeard.PROXY_SETTING = proxy_setting
-        sickbeard.PROXY_INDEXERS = config.checkbox_to_value(proxy_indexers)
-        sickbeard.GIT_PATH = git_path
-        sickbeard.GIT_REMOTE = git_remote
-        sickbeard.CALENDAR_UNPROTECTED = config.checkbox_to_value(calendar_unprotected)
-        # sickbeard.LOG_DIR is set in config.change_LOG_DIR()
+                with epObj.lock:
+                    # don't let them mess up UNAIRED episodes
+                    if epObj.status == UNAIRED:
+                        logger.log(u"Refusing to change status of " + curEp + " because it is UNAIRED", logger.ERROR)
+                        continue
 
-        sickbeard.WEB_PORT = config.to_int(web_port)
-        sickbeard.WEB_IPV6 = config.checkbox_to_value(web_ipv6)
-        # sickbeard.WEB_LOG is set in config.change_LOG_DIR()
-        sickbeard.ENCRYPTION_VERSION = config.checkbox_to_value(encryption_version)
-        sickbeard.WEB_USERNAME = web_username
-        sickbeard.WEB_PASSWORD = web_password
+                    if int(
+                            status) in Quality.DOWNLOADED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED + [
+                        IGNORED] and not ek.ek(os.path.isfile, epObj.location):
+                        logger.log(
+                            u"Refusing to change status of " + curEp + " to DOWNLOADED because it's not SNATCHED/DOWNLOADED",
+                            logger.ERROR)
+                        continue
 
-        sickbeard.FUZZY_DATING = config.checkbox_to_value(fuzzy_dating)
-        sickbeard.TRIM_ZERO = config.checkbox_to_value(trim_zero)
+                    if int(
+                            status) == FAILED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED:
+                        logger.log(
+                            u"Refusing to change status of " + curEp + " to FAILED because it's not SNATCHED/DOWNLOADED",
+                            logger.ERROR)
+                        continue
 
-        if date_preset:
-            sickbeard.DATE_PRESET = date_preset
-            discarded_na_data = date_preset_na
+                    epObj.status = int(status)
 
-        if indexer_default:
-            sickbeard.INDEXER_DEFAULT = config.to_int(indexer_default)
+                    # mass add to database
+                    sql_l.append(epObj.get_sql())
 
-        if indexer_timeout:
-            sickbeard.INDEXER_TIMEOUT = config.to_int(indexer_timeout)
+            if len(sql_l) > 0:
+                myDB = db.DBConnection()
+                myDB.mass_action(sql_l)
 
-        if time_preset:
-            sickbeard.TIME_PRESET_W_SECONDS = time_preset
-            sickbeard.TIME_PRESET = sickbeard.TIME_PRESET_W_SECONDS.replace(u":%S", u"")
+        if int(status) == WANTED:
+            msg = "Backlog was automatically started for the following seasons of <b>" + showObj.name + "</b>:<br />"
+            msg += '<ul>'
 
-        sickbeard.TIMEZONE_DISPLAY = timezone_display
+            for season, segment in segments.items():
+                cur_backlog_queue_item = search_queue.BacklogQueueItem(showObj, segment)
+                sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item)
 
-        if not config.change_LOG_DIR(log_dir, web_log):
-            results += ["Unable to create directory " + os.path.normpath(log_dir) + ", log directory not changed."]
+                msg += "<li>Season " + str(season) + "</li>"
+                logger.log(u"Sending backlog for " + showObj.name + " season " + str(
+                    season) + " because some eps were set to wanted")
 
-        sickbeard.USE_API = config.checkbox_to_value(use_api)
-        sickbeard.API_KEY = api_key
+            msg += "</ul>"
 
-        sickbeard.ENABLE_HTTPS = config.checkbox_to_value(enable_https)
+            if segments:
+                ui.notifications.message("Backlog started", msg)
 
-        if not config.change_HTTPS_CERT(https_cert):
-            results += [
-                "Unable to create directory " + os.path.normpath(https_cert) + ", https cert directory not changed."]
+        if int(status) == FAILED:
+            msg = "Retrying Search was automatically started for the following season of <b>" + showObj.name + "</b>:<br />"
+            msg += '<ul>'
 
-        if not config.change_HTTPS_KEY(https_key):
-            results += [
-                "Unable to create directory " + os.path.normpath(https_key) + ", https key directory not changed."]
+            for season, segment in segments.items():
+                cur_failed_queue_item = search_queue.FailedQueueItem(showObj, [segment])
+                sickbeard.searchQueueScheduler.action.add_item(cur_failed_queue_item)
 
-        sickbeard.HANDLE_REVERSE_PROXY = config.checkbox_to_value(handle_reverse_proxy)
+                msg += "<li>Season " + str(season) + "</li>"
+                logger.log(u"Retrying Search for " + showObj.name + " season " + str(
+                    season) + " because some eps were set to failed")
 
-        sickbeard.THEME_NAME = theme_name
+            msg += "</ul>"
 
-        sickbeard.save_config()
+            if segments:
+                ui.notifications.message("Retry Search started", msg)
 
-        if len(results) > 0:
-            for x in results:
-                logger.log(x, logger.ERROR)
-            ui.notifications.error('Error(s) Saving Configuration',
-                                   '<br />\n'.join(results))
+        if direct:
+            return json.dumps({'result': 'success'})
         else:
-            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
+            return self.redirect("/home/displayShow?show=" + show)
 
-        redirect("/config/general/")
 
+    def testRename(self, show=None):
 
-class ConfigBackupRestore(MainHandler):
-    def index(self, *args, **kwargs):
-        t = PageTemplate(headers=self.request.headers, file="config_backuprestore.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+        if show is None:
+            return self._genericMessage("Error", "You must specify a show")
 
-    def backup(self, backupDir=None):
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-        finalResult = ''
+        if showObj is None:
+            return self._genericMessage("Error", "Show not in show list")
 
-        if backupDir:
-            source = [os.path.join(sickbeard.DATA_DIR, 'sickbeard.db'), sickbeard.CONFIG_FILE]
-            target = os.path.join(backupDir, 'sickrage-' + time.strftime('%Y%m%d%H%M%S') + '.zip')
+        try:
+            show_loc = showObj.location  # @UnusedVariable
+        except exceptions.ShowDirNotFoundException:
+            return self._genericMessage("Error", "Can't rename episodes when the show dir is missing.")
 
-            if helpers.makeZip(source, target):
-                finalResult += "Successful backup to " + target
-            else:
-                finalResult += "Backup FAILED"
-        else:
-            finalResult += "You need to choose a folder to save your backup to!"
+        ep_obj_rename_list = []
 
-        finalResult += "<br />\n"
+        ep_obj_list = showObj.getAllEpisodes(has_location=True)
 
-        return finalResult
+        for cur_ep_obj in ep_obj_list:
+            # Only want to rename if we have a location
+            if cur_ep_obj.location:
+                if cur_ep_obj.relatedEps:
+                    # do we have one of multi-episodes in the rename list already
+                    have_already = False
+                    for cur_related_ep in cur_ep_obj.relatedEps + [cur_ep_obj]:
+                        if cur_related_ep in ep_obj_rename_list:
+                            have_already = True
+                            break
+                        if not have_already:
+                            ep_obj_rename_list.append(cur_ep_obj)
+                else:
+                    ep_obj_rename_list.append(cur_ep_obj)
 
+        if ep_obj_rename_list:
+            # present season DESC episode DESC on screen
+            ep_obj_rename_list.reverse()
 
-    def restore(self, backupFile=None):
+        t = PageTemplate(rh=self, file="testRename.tmpl")
+        t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}]
+        t.ep_obj_list = ep_obj_rename_list
+        t.show = showObj
 
-        finalResult = ''
+        return t.respond()
 
-        if backupFile:
-            source = backupFile
-            target_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
 
-            if helpers.extractZip(source, target_dir):
-                finalResult += "Successfully extracted restore files to " + target_dir
-                finalResult += "<br>Restart sickrage to complete the restore."
-            else:
-                finalResult += "Restore FAILED"
-        else:
-            finalResult += "You need to select a backup file to restore!"
+    def doRename(self, show=None, eps=None):
 
-        finalResult += "<br />\n"
+        if show is None or eps is None:
+            errMsg = "You must specify a show and at least one episode"
+            return self._genericMessage("Error", errMsg)
 
-        return finalResult
+        show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
+        if show_obj is None:
+            errMsg = "Error", "Show not in show list"
+            return self._genericMessage("Error", errMsg)
 
-class ConfigSearch(MainHandler):
-    def index(self, *args, **kwargs):
+        try:
+            show_loc = show_obj.location  # @UnusedVariable
+        except exceptions.ShowDirNotFoundException:
+            return self._genericMessage("Error", "Can't rename episodes when the show dir is missing.")
 
-        t = PageTemplate(headers=self.request.headers, file="config_search.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+        if eps is None:
+            return self.redirect("/home/displayShow?show=" + show)
 
+        myDB = db.DBConnection()
+        for curEp in eps.split('|'):
 
-    def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None,
-                   sab_apikey=None, sab_category=None, sab_category_anime=None, sab_host=None, nzbget_username=None,
-                   nzbget_password=None, nzbget_category=None, nzbget_category_anime=None, nzbget_priority=None,
-                   nzbget_host=None, nzbget_use_https=None, backlog_days=None, backlog_frequency=None,
-                   dailysearch_frequency=None, nzb_method=None, torrent_method=None, usenet_retention=None,
-                   download_propers=None, check_propers_interval=None, allow_high_priority=None,
-                   backlog_startup=None, dailysearch_startup=None,
-                   torrent_dir=None, torrent_username=None, torrent_password=None, torrent_host=None,
-                   torrent_label=None, torrent_label_anime=None, torrent_path=None, torrent_verify_cert=None,
-                   torrent_seed_time=None, torrent_paused=None, torrent_high_bandwidth=None, ignore_words=None,
-                   require_words=None):
+            epInfo = curEp.split('x')
 
-        results = []
+            # this is probably the worst possible way to deal with double eps but I've kinda painted myself into a corner here with this stupid database
+            ep_result = myDB.select(
+                "SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND 5=5",
+                [show, epInfo[0], epInfo[1]])
+            if not ep_result:
+                logger.log(u"Unable to find an episode for " + curEp + ", skipping", logger.WARNING)
+                continue
+            related_eps_result = myDB.select("SELECT * FROM tv_episodes WHERE location = ? AND episode != ?",
+                                             [ep_result[0]["location"], epInfo[1]])
 
-        if not config.change_NZB_DIR(nzb_dir):
-            results += ["Unable to create directory " + os.path.normpath(nzb_dir) + ", dir not changed."]
+            root_ep_obj = show_obj.getEpisode(int(epInfo[0]), int(epInfo[1]))
+            root_ep_obj.relatedEps = []
 
-        if not config.change_TORRENT_DIR(torrent_dir):
-            results += ["Unable to create directory " + os.path.normpath(torrent_dir) + ", dir not changed."]
+            for cur_related_ep in related_eps_result:
+                related_ep_obj = show_obj.getEpisode(int(cur_related_ep["season"]), int(cur_related_ep["episode"]))
+                if related_ep_obj not in root_ep_obj.relatedEps:
+                    root_ep_obj.relatedEps.append(related_ep_obj)
 
-        config.change_DAILYSEARCH_FREQUENCY(dailysearch_frequency)
+            root_ep_obj.rename()
 
-        config.change_BACKLOG_FREQUENCY(backlog_frequency)
-        sickbeard.BACKLOG_DAYS = config.to_int(backlog_days, default=7)
+        return self.redirect("/home/displayShow?show=" + show)
 
-        sickbeard.USE_NZBS = config.checkbox_to_value(use_nzbs)
-        sickbeard.USE_TORRENTS = config.checkbox_to_value(use_torrents)
+    def searchEpisode(self, show=None, season=None, episode=None):
 
-        sickbeard.NZB_METHOD = nzb_method
-        sickbeard.TORRENT_METHOD = torrent_method
-        sickbeard.USENET_RETENTION = config.to_int(usenet_retention, default=500)
+        # retrieve the episode object and fail if we can't get one
+        ep_obj = self._getEpisode(show, season, episode)
+        if isinstance(ep_obj, str):
+            return json.dumps({'result': 'failure'})
 
-        sickbeard.IGNORE_WORDS = ignore_words if ignore_words else ""
-        sickbeard.REQUIRE_WORDS = require_words if require_words else ""
+        # make a queue item for it and put it on the queue
+        ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj.show, ep_obj)
 
-        sickbeard.DOWNLOAD_PROPERS = config.checkbox_to_value(download_propers)
-        sickbeard.CHECK_PROPERS_INTERVAL = check_propers_interval
+        sickbeard.searchQueueScheduler.action.add_item(ep_queue_item)
 
-        sickbeard.ALLOW_HIGH_PRIORITY = config.checkbox_to_value(allow_high_priority)
+        if not ep_queue_item.started and ep_queue_item.success is None:
+            return json.dumps(
+                {'result': 'success'})  # I Actually want to call it queued, because the search hasnt been started yet!
+        if ep_queue_item.started and ep_queue_item.success is None:
+            return json.dumps({'result': 'success'})
+        else:
+            return json.dumps({'result': 'failure'})
 
-        sickbeard.DAILYSEARCH_STARTUP = config.checkbox_to_value(dailysearch_startup)
-        sickbeard.BACKLOG_STARTUP = config.checkbox_to_value(backlog_startup)
-
-        sickbeard.SAB_USERNAME = sab_username
-        sickbeard.SAB_PASSWORD = sab_password
-        sickbeard.SAB_APIKEY = sab_apikey.strip()
-        sickbeard.SAB_CATEGORY = sab_category
-        sickbeard.SAB_CATEGORY_ANIME = sab_category_anime
-        sickbeard.SAB_HOST = config.clean_url(sab_host)
-
-        sickbeard.NZBGET_USERNAME = nzbget_username
-        sickbeard.NZBGET_PASSWORD = nzbget_password
-        sickbeard.NZBGET_CATEGORY = nzbget_category
-        sickbeard.NZBGET_CATEGORY_ANIME = nzbget_category_anime
-        sickbeard.NZBGET_HOST = config.clean_host(nzbget_host)
-        sickbeard.NZBGET_USE_HTTPS = config.checkbox_to_value(nzbget_use_https)
-        sickbeard.NZBGET_PRIORITY = config.to_int(nzbget_priority, default=100)
+    ### Returns the current ep_queue_item status for the current viewed show.
+    # Possible status: Downloaded, Snatched, etc...
+    # Returns {'show': 279530, 'episodes' : ['episode' : 6, 'season' : 1, 'searchstatus' : 'queued', 'status' : 'running', 'quality': '4013']
+    def getManualSearchStatus(self, show=None, season=None):
 
-        sickbeard.TORRENT_USERNAME = torrent_username
-        sickbeard.TORRENT_PASSWORD = torrent_password
-        sickbeard.TORRENT_LABEL = torrent_label
-        sickbeard.TORRENT_LABEL_ANIME = torrent_label_anime
-        sickbeard.TORRENT_VERIFY_CERT = config.checkbox_to_value(torrent_verify_cert)
-        sickbeard.TORRENT_PATH = torrent_path
-        sickbeard.TORRENT_SEED_TIME = torrent_seed_time
-        sickbeard.TORRENT_PAUSED = config.checkbox_to_value(torrent_paused)
-        sickbeard.TORRENT_HIGH_BANDWIDTH = config.checkbox_to_value(torrent_high_bandwidth)
-        sickbeard.TORRENT_HOST = config.clean_url(torrent_host)
+        episodes = []
 
-        sickbeard.save_config()
+        def getEpisodes(searchThread, searchstatus):
+            results = []
 
-        if len(results) > 0:
-            for x in results:
-                logger.log(x, logger.ERROR)
-            ui.notifications.error('Error(s) Saving Configuration',
-                                   '<br />\n'.join(results))
-        else:
-            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
+            if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem):
+                results.append({'episode': searchThread.segment.episode,
+                                'episodeindexid': searchThread.segment.indexerid,
+                                'season': searchThread.segment.season,
+                                'searchstatus': searchstatus,
+                                'status': statusStrings[searchThread.segment.status],
+                                'quality': self.getQualityClass(searchThread.segment)})
+            else:
+                for epObj in searchThread.segment:
+                    results.append({'episode': epObj.episode,
+                                    'episodeindexid': epObj.indexerid,
+                                    'season': epObj.season,
+                                    'searchstatus': searchstatus,
+                                    'status': statusStrings[epObj.status],
+                                    'quality': self.getQualityClass(epObj)})
 
-        redirect("/config/search/")
+            return results
 
+        # Queued Searches
+        for searchThread in sickbeard.searchQueueScheduler.action.get_all_ep_from_queue(show):
+            episodes += getEpisodes(searchThread, 'queued')
 
-class ConfigPostProcessing(MainHandler):
-    def index(self, *args, **kwargs):
+        # Running Searches
+        if (sickbeard.searchQueueScheduler.action.is_manualsearch_in_progress()):
+            searchThread = sickbeard.searchQueueScheduler.action.currentItem
+            if searchThread.success:
+                searchstatus = 'finished'
+            else:
+                searchstatus = 'searching'
+            episodes += getEpisodes(searchThread, searchstatus)
 
-        t = PageTemplate(headers=self.request.headers, file="config_postProcessing.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+        # Finished Searches
+        for searchThread in sickbeard.search_queue.MANUAL_SEARCH_HISTORY:
+            if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem):
+                if str(searchThread.show.indexerid) == show and not [x for x in episodes if x[
+                    'episodeindexid'] == searchThread.segment.indexerid]:
+                    searchstatus = 'finished'
+                    episodes += getEpisodes(searchThread, searchstatus)
+            else:
+                ### These are only Failed Downloads/Retry SearchThreadItems.. lets loop through the segement/episodes
+                if str(searchThread.show.indexerid) == show:
+                    for epObj in searchThread.segment:
+                        if not [x for x in episodes if x['episodeindexid'] == epObj.indexerid]:
+                            searchstatus = 'finished'
+                            episodes += getEpisodes(searchThread, searchstatus)
 
+        return json.dumps({'show': show, 'episodes': episodes})
 
-    def savePostProcessing(self, naming_pattern=None, naming_multi_ep=None,
-                           xbmc_data=None, xbmc_12plus_data=None, mediabrowser_data=None, sony_ps3_data=None,
-                           wdtv_data=None, tivo_data=None, mede8er_data=None,
-                           keep_processed_dir=None, process_method=None, process_automatically=None,
-                           rename_episodes=None, airdate_episodes=None, unpack=None,
-                           move_associated_files=None, postpone_if_sync_files=None, nfo_rename=None,
-                           tv_download_dir=None, naming_custom_abd=None,
-                           naming_anime=None,
-                           naming_abd_pattern=None, naming_strip_year=None, use_failed_downloads=None,
-                           delete_failed=None, extra_scripts=None, skip_removed_files=None,
-                           naming_custom_sports=None, naming_sports_pattern=None,
-                           naming_custom_anime=None, naming_anime_pattern=None, naming_anime_multi_ep=None,
-                           autopostprocesser_frequency=None):
+    def getQualityClass(self, ep_obj):
+        # return the correct json value
 
-        results = []
+        # Find the quality class for the episode
+        quality_class = Quality.qualityStrings[Quality.UNKNOWN]
+        ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status)
+        for x in (SD, HD720p, HD1080p):
+            if ep_quality in Quality.splitQuality(x)[0]:
+                quality_class = qualityPresetStrings[x]
+                break
 
-        if not config.change_TV_DOWNLOAD_DIR(tv_download_dir):
-            results += ["Unable to create directory " + os.path.normpath(tv_download_dir) + ", dir not changed."]
+        return quality_class
 
-        sickbeard.PROCESS_AUTOMATICALLY = config.checkbox_to_value(process_automatically)
-        config.change_AUTOPOSTPROCESSER_FREQUENCY(autopostprocesser_frequency)
+    def searchEpisodeSubtitles(self, show=None, season=None, episode=None):
+        # retrieve the episode object and fail if we can't get one
+        ep_obj = self._getEpisode(show, season, episode)
+        if isinstance(ep_obj, str):
+            return json.dumps({'result': 'failure'})
 
-        if sickbeard.PROCESS_AUTOMATICALLY and not sickbeard.autoPostProcesserScheduler.isAlive():
-            sickbeard.autoPostProcesserScheduler.silent = False
-            try:
-                sickbeard.autoPostProcesserScheduler.start()
-            except:
-                pass
-        elif not sickbeard.PROCESS_AUTOMATICALLY:
-            sickbeard.autoPostProcesserScheduler.stop.set()
-            sickbeard.autoPostProcesserScheduler.silent = True
-            try:
-                sickbeard.autoPostProcesserScheduler.join(5)
-            except:
-                pass
+        # try do download subtitles for that episode
+        previous_subtitles = set(subliminal.language.Language(x) for x in ep_obj.subtitles)
+        try:
+            ep_obj.subtitles = set(x.language for x in ep_obj.downloadSubtitles().values()[0])
+        except:
+            return json.dumps({'result': 'failure'})
 
-        if unpack:
-            if self.isRarSupported() != 'not supported':
-                sickbeard.UNPACK = config.checkbox_to_value(unpack)
-            else:
-                sickbeard.UNPACK = 0
-                results.append("Unpacking Not Supported, disabling unpack setting")
+        # return the correct json value
+        if previous_subtitles != ep_obj.subtitles:
+            status = 'New subtitles downloaded: %s' % ' '.join([
+                "<img src='" + sickbeard.WEB_ROOT + "/images/flags/" + x.alpha2 +
+                ".png' alt='" + x.name + "'/>" for x in
+                sorted(list(ep_obj.subtitles.difference(previous_subtitles)))])
         else:
-            sickbeard.UNPACK = config.checkbox_to_value(unpack)
-
-        sickbeard.KEEP_PROCESSED_DIR = config.checkbox_to_value(keep_processed_dir)
-        sickbeard.PROCESS_METHOD = process_method
-        sickbeard.EXTRA_SCRIPTS = [x.strip() for x in extra_scripts.split('|') if x.strip()]
-        sickbeard.RENAME_EPISODES = config.checkbox_to_value(rename_episodes)
-        sickbeard.AIRDATE_EPISODES = config.checkbox_to_value(airdate_episodes)
-        sickbeard.MOVE_ASSOCIATED_FILES = config.checkbox_to_value(move_associated_files)
-        sickbeard.POSTPONE_IF_SYNC_FILES = config.checkbox_to_value(postpone_if_sync_files)
-        sickbeard.NAMING_CUSTOM_ABD = config.checkbox_to_value(naming_custom_abd)
-        sickbeard.NAMING_CUSTOM_SPORTS = config.checkbox_to_value(naming_custom_sports)
-        sickbeard.NAMING_CUSTOM_ANIME = config.checkbox_to_value(naming_custom_anime)
-        sickbeard.NAMING_STRIP_YEAR = config.checkbox_to_value(naming_strip_year)
-        sickbeard.USE_FAILED_DOWNLOADS = config.checkbox_to_value(use_failed_downloads)
-        sickbeard.DELETE_FAILED = config.checkbox_to_value(delete_failed)
-        sickbeard.SKIP_REMOVED_FILES = config.checkbox_to_value(skip_removed_files)
-        sickbeard.NFO_RENAME = config.checkbox_to_value(nfo_rename)
+            status = 'No subtitles downloaded'
+        ui.notifications.message('Subtitles Search', status)
+        return json.dumps({'result': status, 'subtitles': ','.join(sorted([x.alpha2 for x in
+                                                                           ep_obj.subtitles.union(
+                                                                               previous_subtitles)]))})
 
-        sickbeard.METADATA_XBMC = xbmc_data
-        sickbeard.METADATA_XBMC_12PLUS = xbmc_12plus_data
-        sickbeard.METADATA_MEDIABROWSER = mediabrowser_data
-        sickbeard.METADATA_PS3 = sony_ps3_data
-        sickbeard.METADATA_WDTV = wdtv_data
-        sickbeard.METADATA_TIVO = tivo_data
-        sickbeard.METADATA_MEDE8ER = mede8er_data
+    def setSceneNumbering(self, show, indexer, forSeason=None, forEpisode=None, forAbsolute=None, sceneSeason=None,
+                          sceneEpisode=None, sceneAbsolute=None):
 
-        sickbeard.metadata_provider_dict['XBMC'].set_config(sickbeard.METADATA_XBMC)
-        sickbeard.metadata_provider_dict['XBMC 12+'].set_config(sickbeard.METADATA_XBMC_12PLUS)
-        sickbeard.metadata_provider_dict['MediaBrowser'].set_config(sickbeard.METADATA_MEDIABROWSER)
-        sickbeard.metadata_provider_dict['Sony PS3'].set_config(sickbeard.METADATA_PS3)
-        sickbeard.metadata_provider_dict['WDTV'].set_config(sickbeard.METADATA_WDTV)
-        sickbeard.metadata_provider_dict['TIVO'].set_config(sickbeard.METADATA_TIVO)
-        sickbeard.metadata_provider_dict['Mede8er'].set_config(sickbeard.METADATA_MEDE8ER)
+        # sanitize:
+        if forSeason in ['null', '']: forSeason = None
+        if forEpisode in ['null', '']: forEpisode = None
+        if forAbsolute in ['null', '']: forAbsolute = None
+        if sceneSeason in ['null', '']: sceneSeason = None
+        if sceneEpisode in ['null', '']: sceneEpisode = None
+        if sceneAbsolute in ['null', '']: sceneAbsolute = None
 
-        if self.isNamingValid(naming_pattern, naming_multi_ep, anime_type=naming_anime) != "invalid":
-            sickbeard.NAMING_PATTERN = naming_pattern
-            sickbeard.NAMING_MULTI_EP = int(naming_multi_ep)
-            sickbeard.NAMING_ANIME = int(naming_anime)
-            sickbeard.NAMING_FORCE_FOLDERS = naming.check_force_season_folders()
-        else:
-            if int(naming_anime) in [1, 2]:
-                results.append("You tried saving an invalid anime naming config, not saving your naming settings")
-            else:
-                results.append("You tried saving an invalid naming config, not saving your naming settings")
+        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-        if self.isNamingValid(naming_anime_pattern, naming_anime_multi_ep, anime_type=naming_anime) != "invalid":
-            sickbeard.NAMING_ANIME_PATTERN = naming_anime_pattern
-            sickbeard.NAMING_ANIME_MULTI_EP = int(naming_anime_multi_ep)
-            sickbeard.NAMING_ANIME = int(naming_anime)
-            sickbeard.NAMING_FORCE_FOLDERS = naming.check_force_season_folders()
+        if showObj.is_anime:
+            result = {
+                'success': True,
+                'forAbsolute': forAbsolute,
+            }
         else:
-            if int(naming_anime) in [1, 2]:
-                results.append("You tried saving an invalid anime naming config, not saving your naming settings")
-            else:
-                results.append("You tried saving an invalid naming config, not saving your naming settings")
+            result = {
+                'success': True,
+                'forSeason': forSeason,
+                'forEpisode': forEpisode,
+            }
 
-        if self.isNamingValid(naming_abd_pattern, None, abd=True) != "invalid":
-            sickbeard.NAMING_ABD_PATTERN = naming_abd_pattern
+        # retrieve the episode object and fail if we can't get one
+        if showObj.is_anime:
+            ep_obj = self._getEpisode(show, absolute=forAbsolute)
         else:
-            results.append(
-                "You tried saving an invalid air-by-date naming config, not saving your air-by-date settings")
+            ep_obj = self._getEpisode(show, forSeason, forEpisode)
 
-        if self.isNamingValid(naming_sports_pattern, None, sports=True) != "invalid":
-            sickbeard.NAMING_SPORTS_PATTERN = naming_sports_pattern
-        else:
-            results.append(
-                "You tried saving an invalid sports naming config, not saving your sports settings")
+        if isinstance(ep_obj, str):
+            result['success'] = False
+            result['errorMessage'] = ep_obj
+        elif showObj.is_anime:
+            logger.log(u"setAbsoluteSceneNumbering for %s from %s to %s" %
+                       (show, forAbsolute, sceneAbsolute), logger.DEBUG)
 
-        sickbeard.save_config()
+            show = int(show)
+            indexer = int(indexer)
+            forAbsolute = int(forAbsolute)
+            if sceneAbsolute is not None: sceneAbsolute = int(sceneAbsolute)
 
-        if len(results) > 0:
-            for x in results:
-                logger.log(x, logger.ERROR)
-            ui.notifications.error('Error(s) Saving Configuration',
-                                   '<br />\n'.join(results))
+            set_scene_numbering(show, indexer, absolute_number=forAbsolute, sceneAbsolute=sceneAbsolute)
         else:
-            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
-
-        redirect("/config/postProcessing/")
+            logger.log(u"setEpisodeSceneNumbering for %s from %sx%s to %sx%s" %
+                       (show, forSeason, forEpisode, sceneSeason, sceneEpisode), logger.DEBUG)
 
-    def testNaming(self, pattern=None, multi=None, abd=False, sports=False, anime_type=None):
+            show = int(show)
+            indexer = int(indexer)
+            forSeason = int(forSeason)
+            forEpisode = int(forEpisode)
+            if sceneSeason is not None: sceneSeason = int(sceneSeason)
+            if sceneEpisode is not None: sceneEpisode = int(sceneEpisode)
 
-        if multi is not None:
-            multi = int(multi)
+            set_scene_numbering(show, indexer, season=forSeason, episode=forEpisode, sceneSeason=sceneSeason,
+                                sceneEpisode=sceneEpisode)
 
-        if anime_type is not None:
-            anime_type = int(anime_type)
+        if showObj.is_anime:
+            sn = get_scene_absolute_numbering(show, indexer, forAbsolute)
+            if sn:
+                result['sceneAbsolute'] = sn
+            else:
+                result['sceneAbsolute'] = None
+        else:
+            sn = get_scene_numbering(show, indexer, forSeason, forEpisode)
+            if sn:
+                (result['sceneSeason'], result['sceneEpisode']) = sn
+            else:
+                (result['sceneSeason'], result['sceneEpisode']) = (None, None)
 
-        result = naming.test_name(pattern, multi, abd, sports, anime_type)
+        return json.dumps(result)
 
-        result = ek.ek(os.path.join, result['dir'], result['name'])
 
-        return result
+    def retryEpisode(self, show, season, episode):
 
+        # retrieve the episode object and fail if we can't get one
+        ep_obj = self._getEpisode(show, season, episode)
+        if isinstance(ep_obj, str):
+            return json.dumps({'result': 'failure'})
 
-    def isNamingValid(self, pattern=None, multi=None, abd=False, sports=False, anime_type=None):
-        if pattern is None:
-            return "invalid"
+        # make a queue item for it and put it on the queue
+        ep_queue_item = search_queue.FailedQueueItem(ep_obj.show, [ep_obj])
+        sickbeard.searchQueueScheduler.action.add_item(ep_queue_item)
 
-        if multi is not None:
-            multi = int(multi)
+        if not ep_queue_item.started and ep_queue_item.success is None:
+            return json.dumps(
+                {'result': 'success'})  # I Actually want to call it queued, because the search hasnt been started yet!
+        if ep_queue_item.started and ep_queue_item.success is None:
+            return json.dumps({'result': 'success'})
+        else:
+            return json.dumps({'result': 'failure'})
 
-        if anime_type is not None:
-            anime_type = int(anime_type)
 
-        # air by date shows just need one check, we don't need to worry about season folders
-        if abd:
-            is_valid = naming.check_valid_abd_naming(pattern)
-            require_season_folders = False
+@route('/home/postprocess(/?.*)')
+class HomePostProcess(Home):
+    def __init__(self, *args, **kwargs):
+        super(HomePostProcess, self).__init__(*args, **kwargs)
 
-        # sport shows just need one check, we don't need to worry about season folders
-        elif sports:
-            is_valid = naming.check_valid_sports_naming(pattern)
-            require_season_folders = False
+    def index(self):
+        t = PageTemplate(rh=self, file="home_postprocess.tmpl")
+        t.submenu = self.HomeMenu()
+        return t.respond()
 
-        else:
-            # check validity of single and multi ep cases for the whole path
-            is_valid = naming.check_valid_naming(pattern, multi, anime_type)
+    def processEpisode(self, dir=None, nzbName=None, jobName=None, quiet=None, process_method=None, force=None,
+                       is_priority=None, failed="0", type="auto", *args, **kwargs):
 
-            # check validity of single and multi ep cases for only the file name
-            require_season_folders = naming.check_force_season_folders(pattern, multi, anime_type)
+        if failed == "0":
+            failed = False
+        else:
+            failed = True
 
-        if is_valid and not require_season_folders:
-            return "valid"
-        elif is_valid and require_season_folders:
-            return "seasonfolders"
+        if force in ["on", "1"]:
+            force = True
         else:
-            return "invalid"
+            force = False
 
+        if is_priority in ["on", "1"]:
+            is_priority = True
+        else:
+            is_priority = False
 
-    def isRarSupported(self, *args, **kwargs):
-        """
-        Test Packing Support:
-            - Simulating in memory rar extraction on test.rar file
-        """
+        if not dir:
+            return self.redirect("/home/postprocess/")
+        else:
+            result = processTV.processDir(dir, nzbName, process_method=process_method, force=force,
+                                          is_priority=is_priority, failed=failed, type=type)
+            if quiet is not None and int(quiet) == 1:
+                return result
 
-        try:
-            rar_path = os.path.join(sickbeard.PROG_DIR, 'lib', 'unrar2', 'test.rar')
-            testing = RarFile(rar_path).read_files('*test.txt')
-            if testing[0][1] == 'This is only a test.':
-                return 'supported'
-            logger.log(u'Rar Not Supported: Can not read the content of test file', logger.ERROR)
-            return 'not supported'
-        except Exception, e:
-            logger.log(u'Rar Not Supported: ' + ex(e), logger.ERROR)
-            return 'not supported'
+            result = result.replace("\n", "<br />\n")
+            return self._genericMessage("Postprocessing results", result)
 
 
-class ConfigProviders(MainHandler):
-    def index(self, *args, **kwargs):
-        t = PageTemplate(headers=self.request.headers, file="config_providers.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+@route('/home/addShows(/?.*)')
+class HomeAddShows(Home):
+    def __init__(self, *args, **kwargs):
+        super(HomeAddShows, self).__init__(*args, **kwargs)
 
+    def index(self):
+        t = PageTemplate(rh=self, file="home_addShows.tmpl")
+        t.submenu = self.HomeMenu()
+        return t.respond()
 
-    def canAddNewznabProvider(self, name):
+    def getIndexerLanguages(self):
+        result = sickbeard.indexerApi().config['valid_languages']
 
-        if not name:
-            return json.dumps({'error': 'No Provider Name specified'})
+        # Make sure list is sorted alphabetically but 'en' is in front
+        if 'en' in result:
+            del result[result.index('en')]
+        result.sort()
+        result.insert(0, 'en')
 
-        providerDict = dict(zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
+        return json.dumps({'results': result})
 
-        tempProvider = newznab.NewznabProvider(name, '')
 
-        if tempProvider.getID() in providerDict:
-            return json.dumps({'error': 'Provider Name already exists as ' + providerDict[tempProvider.getID()].name})
-        else:
-            return json.dumps({'success': tempProvider.getID()})
+    def sanitizeFileName(self, name):
+        return helpers.sanitizeFileName(name)
 
 
-    def saveNewznabProvider(self, name, url, key=''):
+    def searchIndexersForShowName(self, search_term, lang="en", indexer=None):
+        if not lang or lang == 'null':
+            lang = "en"
 
-        if not name or not url:
-            return '0'
+        search_term = search_term.encode('utf-8')
 
-        providerDict = dict(zip([x.name for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
+        results = {}
+        final_results = []
 
-        if name in providerDict:
-            if not providerDict[name].default:
-                providerDict[name].name = name
-                providerDict[name].url = config.clean_url(url)
+        # Query Indexers for each search term and build the list of results
+        for indexer in sickbeard.indexerApi().indexers if not int(indexer) else [int(indexer)]:
+            lINDEXER_API_PARMS = sickbeard.indexerApi(indexer).api_params.copy()
+            lINDEXER_API_PARMS['language'] = lang
+            lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsListUI
+            t = sickbeard.indexerApi(indexer).indexer(**lINDEXER_API_PARMS)
 
-            providerDict[name].key = key
-            # a 0 in the key spot indicates that no key is needed
-            if key == '0':
-                providerDict[name].needs_auth = False
-            else:
-                providerDict[name].needs_auth = True
+            logger.log("Searching for Show with searchterm: %s on Indexer: %s" % (
+                search_term, sickbeard.indexerApi(indexer).name), logger.DEBUG)
+            try:
+                # add search results
+                results.setdefault(indexer, []).extend(t[search_term])
+            except Exception, e:
+                continue
 
-            return providerDict[name].getID() + '|' + providerDict[name].configStr()
+        map(final_results.extend,
+            ([[sickbeard.indexerApi(id).name, id, sickbeard.indexerApi(id).config["show_url"], int(show['id']),
+               show['seriesname'], show['firstaired']] for show in shows] for id, shows in results.items()))
 
-        else:
-            newProvider = newznab.NewznabProvider(name, url, key=key)
-            sickbeard.newznabProviderList.append(newProvider)
-            return newProvider.getID() + '|' + newProvider.configStr()
+        lang_id = sickbeard.indexerApi().config['langabbv_to_id'][lang]
+        return json.dumps({'results': final_results, 'langid': lang_id})
 
-    def getNewznabCategories(self, name, url, key):
-        '''
-        Retrieves a list of possible categories with category id's
-        Using the default url/api?cat
-        http://yournewznaburl.com/api?t=caps&apikey=yourapikey
-        '''
-        error = ""
-        success = False
 
-        if not name:
-            error += "\nNo Provider Name specified"
-        if not url:
-            error += "\nNo Provider Url specified"
-        if not key:
-            error += "\nNo Provider Api key specified"
+    def massAddTable(self, rootDir=None):
+        t = PageTemplate(rh=self, file="home_massAddTable.tmpl")
+        t.submenu = self.HomeMenu()
 
-        if error <> "":
-            return json.dumps({'success': False, 'error': error})
+        if not rootDir:
+            return "No folders selected."
+        elif type(rootDir) != list:
+            root_dirs = [rootDir]
+        else:
+            root_dirs = rootDir
 
-        # Get list with Newznabproviders
-        # providerDict = dict(zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
+        root_dirs = [urllib.unquote_plus(x) for x in root_dirs]
 
-        # Get newznabprovider obj with provided name
-        tempProvider = newznab.NewznabProvider(name, url, key)
+        if sickbeard.ROOT_DIRS:
+            default_index = int(sickbeard.ROOT_DIRS.split('|')[0])
+        else:
+            default_index = 0
 
-        success, tv_categories, error = tempProvider.get_newznab_categories()
+        if len(root_dirs) > default_index:
+            tmp = root_dirs[default_index]
+            if tmp in root_dirs:
+                root_dirs.remove(tmp)
+                root_dirs = [tmp] + root_dirs
 
-        return json.dumps({'success': success, 'tv_categories': tv_categories, 'error': error})
+        dir_list = []
 
-    def deleteNewznabProvider(self, nnid):
+        myDB = db.DBConnection()
+        for root_dir in root_dirs:
+            try:
+                file_list = ek.ek(os.listdir, root_dir)
+            except:
+                continue
 
-        providerDict = dict(zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
+            for cur_file in file_list:
 
-        if nnid not in providerDict or providerDict[nnid].default:
-            return '0'
+                try:
+                    cur_path = ek.ek(os.path.normpath, ek.ek(os.path.join, root_dir, cur_file))
+                    if not ek.ek(os.path.isdir, cur_path):
+                        continue
+                except:
+                    continue
 
-        # delete it from the list
-        sickbeard.newznabProviderList.remove(providerDict[nnid])
+                cur_dir = {
+                    'dir': cur_path,
+                    'display_dir': '<b>' + ek.ek(os.path.dirname, cur_path) + os.sep + '</b>' + ek.ek(
+                        os.path.basename,
+                        cur_path),
+                }
 
-        if nnid in sickbeard.PROVIDER_ORDER:
-            sickbeard.PROVIDER_ORDER.remove(nnid)
+                # see if the folder is in KODI already
+                dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path])
 
-        return '1'
+                if dirResults:
+                    cur_dir['added_already'] = True
+                else:
+                    cur_dir['added_already'] = False
 
+                dir_list.append(cur_dir)
 
-    def canAddTorrentRssProvider(self, name, url, cookies):
+                indexer_id = show_name = indexer = None
+                for cur_provider in sickbeard.metadata_provider_dict.values():
+                    if not (indexer_id and show_name):
+                        (indexer_id, show_name, indexer) = cur_provider.retrieveShowMetadata(cur_path)
 
-        if not name:
-            return json.dumps({'error': 'Invalid name specified'})
+                        # default to TVDB if indexer was not detected
+                        if show_name and not (indexer or indexer_id):
+                            (sn, idx, id) = helpers.searchIndexerForShowID(show_name, indexer, indexer_id)
 
-        providerDict = dict(
-            zip([x.getID() for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
+                            # set indexer and indexer_id from found info
+                            if not indexer and idx:
+                                indexer = idx
 
-        tempProvider = rsstorrent.TorrentRssProvider(name, url, cookies)
+                            if not indexer_id and id:
+                                indexer_id = id
 
-        if tempProvider.getID() in providerDict:
-            return json.dumps({'error': 'Exists as ' + providerDict[tempProvider.getID()].name})
-        else:
-            (succ, errMsg) = tempProvider.validateRSS()
-            if succ:
-                return json.dumps({'success': tempProvider.getID()})
-            else:
-                return json.dumps({'error': errMsg})
+                cur_dir['existing_info'] = (indexer_id, show_name, indexer)
 
+                if indexer_id and helpers.findCertainShow(sickbeard.showList, indexer_id):
+                    cur_dir['added_already'] = True
 
-    def saveTorrentRssProvider(self, name, url, cookies):
+        t.dirList = dir_list
 
-        if not name or not url:
-            return '0'
+        return t.respond()
 
-        providerDict = dict(zip([x.name for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
 
-        if name in providerDict:
-            providerDict[name].name = name
-            providerDict[name].url = config.clean_url(url)
-            providerDict[name].cookies = cookies
+    def newShow(self, show_to_add=None, other_shows=None):
+        """
+        Display the new show page which collects a tvdb id, folder, and extra options and
+        posts them to addNewShow
+        """
+        t = PageTemplate(rh=self, file="home_newShow.tmpl")
+        t.submenu = self.HomeMenu()
 
-            return providerDict[name].getID() + '|' + providerDict[name].configStr()
+        indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add)
 
+        if indexer_id and indexer and show_name:
+            use_provided_info = True
         else:
-            newProvider = rsstorrent.TorrentRssProvider(name, url, cookies)
-            sickbeard.torrentRssProviderList.append(newProvider)
-            return newProvider.getID() + '|' + newProvider.configStr()
+            use_provided_info = False
 
+        # tell the template whether we're giving it show name & Indexer ID
+        t.use_provided_info = use_provided_info
 
-    def deleteTorrentRssProvider(self, id):
+        # use the given show_dir for the indexer search if available
+        if not show_dir:
+            t.default_show_name = ''
+        elif not show_name:
+            t.default_show_name = re.sub(' \(\d{4}\)', '',
+                                         ek.ek(os.path.basename, ek.ek(os.path.normpath, show_dir)).replace('.', ' '))
+        else:
+            t.default_show_name = show_name
 
-        providerDict = dict(
-            zip([x.getID() for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
-
-        if id not in providerDict:
-            return '0'
+        # carry a list of other dirs if given
+        if not other_shows:
+            other_shows = []
+        elif type(other_shows) != list:
+            other_shows = [other_shows]
 
-        # delete it from the list
-        sickbeard.torrentRssProviderList.remove(providerDict[id])
+        if use_provided_info:
+            t.provided_indexer_id = int(indexer_id or 0)
+            t.provided_indexer_name = show_name
 
-        if id in sickbeard.PROVIDER_ORDER:
-            sickbeard.PROVIDER_ORDER.remove(id)
+        t.provided_show_dir = show_dir
+        t.other_shows = other_shows
+        t.provided_indexer = int(indexer or sickbeard.INDEXER_DEFAULT)
+        t.indexers = sickbeard.indexerApi().indexers
 
-        return '1'
+        return t.respond()
 
+    def recommendedShows(self):
+        """
+        Display the new show page which collects a tvdb id, folder, and extra options and
+        posts them to addNewShow
+        """
+        t = PageTemplate(rh=self, file="home_recommendedShows.tmpl")
+        t.submenu = self.HomeMenu()
 
-    def saveProviders(self, newznab_string='', torrentrss_string='', provider_order=None, **kwargs):
+        return t.respond()
 
-        results = []
+    def getRecommendedShows(self):
+        final_results = []
 
-        provider_str_list = provider_order.split()
-        provider_list = []
+        logger.log(u"Getting recommended shows from Trakt.tv", logger.DEBUG)
 
-        newznabProviderDict = dict(
-            zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
+        trakt_api = TraktAPI(sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
 
-        finishedNames = []
+        try:
+            recommendedlist = trakt_api.traktRequest("recommendations/shows.json/%APIKEY%")
+
+            if recommendedlist:
+                indexers = ['tvdb_id', 'tvrage_id']
+                map(final_results.append, (
+                    [int(show[indexers[sickbeard.TRAKT_DEFAULT_INDEXER - 1]]), show['url'], show['title'],
+                     show['overview'],
+                     datetime.date.fromtimestamp(int(show['first_aired']) / 1000.0).strftime('%Y%m%d')]
+                    for show in recommendedlist if not helpers.findCertainShow(sickbeard.showList, [
+                    int(show[indexers[sickbeard.TRAKT_DEFAULT_INDEXER - 1]])])))
+        except (traktException, traktAuthException, traktServerBusy) as e:
+            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
 
-        # add all the newznab info we got into our list
-        if newznab_string:
-            for curNewznabProviderStr in newznab_string.split('!!!'):
+        return json.dumps({'results': final_results})
 
-                if not curNewznabProviderStr:
-                    continue
+    def addRecommendedShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
+                           anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
+                           fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None,
+                           scene=None):
 
-                cur_name, cur_url, cur_key, cur_cat = curNewznabProviderStr.split('|')
-                cur_url = config.clean_url(cur_url)
+        indexer = 1
+        indexer_name = sickbeard.indexerApi(int(indexer)).name
+        show_url = whichSeries.split('|')[1]
+        indexer_id = whichSeries.split('|')[0]
+        show_name = whichSeries.split('|')[2]
 
-                newProvider = newznab.NewznabProvider(cur_name, cur_url, key=cur_key)
+        return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]),
+                               indexerLang, rootDir,
+                               defaultStatus,
+                               anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows,
+                               skipShow, providedIndexer, anime, scene)
 
-                cur_id = newProvider.getID()
+    def trendingShows(self):
+        """
+        Display the new show page which collects a tvdb id, folder, and extra options and
+        posts them to addNewShow
+        """
+        t = PageTemplate(rh=self, file="home_trendingShows.tmpl")
+        t.submenu = self.HomeMenu()
 
-                # if it already exists then update it
-                if cur_id in newznabProviderDict:
-                    newznabProviderDict[cur_id].name = cur_name
-                    newznabProviderDict[cur_id].url = cur_url
-                    newznabProviderDict[cur_id].key = cur_key
-                    newznabProviderDict[cur_id].catIDs = cur_cat
-                    # a 0 in the key spot indicates that no key is needed
-                    if cur_key == '0':
-                        newznabProviderDict[cur_id].needs_auth = False
-                    else:
-                        newznabProviderDict[cur_id].needs_auth = True
+        return t.respond()
 
-                    try:
-                        newznabProviderDict[cur_id].search_mode = str(kwargs[cur_id + '_search_mode']).strip()
-                    except:
-                        pass
+    def getTrendingShows(self):
+        """
+        Display the new show page which collects a tvdb id, folder, and extra options and
+        posts them to addNewShow
+        """
+        t = PageTemplate(rh=self, file="trendingShows.tmpl")
+        t.submenu = self.HomeMenu()
 
-                    try:
-                        newznabProviderDict[cur_id].search_fallback = config.checkbox_to_value(
-                            kwargs[cur_id + '_search_fallback'])
-                    except:
-                        newznabProviderDict[cur_id].search_fallback = 0
+        t.trending_shows = []
 
-                    try:
-                        newznabProviderDict[cur_id].enable_daily = config.checkbox_to_value(
-                            kwargs[cur_id + '_enable_daily'])
-                    except:
-                        newznabProviderDict[cur_id].enable_daily = 0
+        trakt_api = TraktAPI(sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
 
-                    try:
-                        newznabProviderDict[cur_id].enable_backlog = config.checkbox_to_value(
-                            kwargs[cur_id + '_enable_backlog'])
-                    except:
-                        newznabProviderDict[cur_id].enable_backlog = 0
-                else:
-                    sickbeard.newznabProviderList.append(newProvider)
+        try:
+            shows = trakt_api.traktRequest("shows/trending.json/%APIKEY%") or []
+            for show in shows:
+                try:
+                    if not helpers.findCertainShow(sickbeard.showList,
+                                                   [int(show['tvdb_id']), int(show['tvrage_id'])]):
+                        t.trending_shows += [show]
+                except exceptions.MultipleShowObjectsException:
+                    continue
+        except (traktException, traktAuthException, traktServerBusy) as e:
+            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
 
-                finishedNames.append(cur_id)
+        return t.respond()
 
-        # delete anything that is missing
-        for curProvider in sickbeard.newznabProviderList:
-            if curProvider.getID() not in finishedNames:
-                sickbeard.newznabProviderList.remove(curProvider)
+    def existingShows(self):
+        """
+        Prints out the page to add existing shows from a root dir
+        """
+        t = PageTemplate(rh=self, file="home_addExistingShow.tmpl")
+        t.submenu = self.HomeMenu()
 
-        torrentRssProviderDict = dict(
-            zip([x.getID() for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
-        finishedNames = []
+        return t.respond()
 
-        if torrentrss_string:
-            for curTorrentRssProviderStr in torrentrss_string.split('!!!'):
+    def addTraktShow(self, indexer_id, showName):
+        if helpers.findCertainShow(sickbeard.showList, int(indexer_id)):
+            return
 
-                if not curTorrentRssProviderStr:
-                    continue
+        if sickbeard.ROOT_DIRS:
+            root_dirs = sickbeard.ROOT_DIRS.split('|')
+            location = root_dirs[int(root_dirs[0]) + 1]
+        else:
+            location = None
 
-                curName, curURL, curCookies = curTorrentRssProviderStr.split('|')
-                curURL = config.clean_url(curURL)
+        if location:
+            show_dir = ek.ek(os.path.join, location, helpers.sanitizeFileName(showName))
+            dir_exists = helpers.makeDir(show_dir)
+            if not dir_exists:
+                logger.log(u"Unable to create the folder " + show_dir + ", can't add the show", logger.ERROR)
+                return
+            else:
+                helpers.chmodAsParent(show_dir)
 
-                newProvider = rsstorrent.TorrentRssProvider(curName, curURL, curCookies)
+            sickbeard.showQueueScheduler.action.addShow(1, int(indexer_id), show_dir,
+                                                        default_status=sickbeard.STATUS_DEFAULT,
+                                                        quality=sickbeard.QUALITY_DEFAULT,
+                                                        flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT,
+                                                        subtitles=sickbeard.SUBTITLES_DEFAULT,
+                                                        anime=sickbeard.ANIME_DEFAULT,
+                                                        scene=sickbeard.SCENE_DEFAULT)
 
-                curID = newProvider.getID()
+            ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
+        else:
+            logger.log(u"There was an error creating the show, no root directory setting found", logger.ERROR)
+            return "No root directories setup, please go back and add one."
 
-                # if it already exists then update it
-                if curID in torrentRssProviderDict:
-                    torrentRssProviderDict[curID].name = curName
-                    torrentRssProviderDict[curID].url = curURL
-                    torrentRssProviderDict[curID].cookies = curCookies
-                else:
-                    sickbeard.torrentRssProviderList.append(newProvider)
+        # done adding show
+        return self.redirect('/home/')
 
-                finishedNames.append(curID)
+    def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
+                   anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
+                   fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None,
+                   scene=None):
+        """
+        Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are
+        provided then it forwards back to newShow, if not it goes to /home.
+        """
 
-        # delete anything that is missing
-        for curProvider in sickbeard.torrentRssProviderList:
-            if curProvider.getID() not in finishedNames:
-                sickbeard.torrentRssProviderList.remove(curProvider)
+        # grab our list of other dirs if given
+        if not other_shows:
+            other_shows = []
+        elif type(other_shows) != list:
+            other_shows = [other_shows]
 
-        # do the enable/disable
-        for curProviderStr in provider_str_list:
-            curProvider, curEnabled = curProviderStr.split(':')
-            curEnabled = config.to_int(curEnabled)
+        def finishAddShow():
+            # if there are no extra shows then go home
+            if not other_shows:
+                return self.redirect('/home/')
 
-            curProvObj = [x for x in sickbeard.providers.sortedProviderList() if
-                          x.getID() == curProvider and hasattr(x, 'enabled')]
-            if curProvObj:
-                curProvObj[0].enabled = bool(curEnabled)
+            # peel off the next one
+            next_show_dir = other_shows[0]
+            rest_of_show_dirs = other_shows[1:]
 
-            provider_list.append(curProvider)
-            if curProvider in newznabProviderDict:
-                newznabProviderDict[curProvider].enabled = bool(curEnabled)
-            elif curProvider in torrentRssProviderDict:
-                torrentRssProviderDict[curProvider].enabled = bool(curEnabled)
+            # go to add the next show
+            return self.newShow(next_show_dir, rest_of_show_dirs)
 
-        # dynamically load provider settings
-        for curTorrentProvider in [curProvider for curProvider in sickbeard.providers.sortedProviderList() if
-                                   curProvider.providerType == sickbeard.GenericProvider.TORRENT]:
+        # if we're skipping then behave accordingly
+        if skipShow:
+            return finishAddShow()
 
-            if hasattr(curTorrentProvider, 'minseed'):
-                try:
-                    curTorrentProvider.minseed = int(str(kwargs[curTorrentProvider.getID() + '_minseed']).strip())
-                except:
-                    curTorrentProvider.minseed = 0
+        # sanity check on our inputs
+        if (not rootDir and not fullShowPath) or not whichSeries:
+            return "Missing params, no Indexer ID or folder:" + repr(whichSeries) + " and " + repr(
+                rootDir) + "/" + repr(fullShowPath)
 
-            if hasattr(curTorrentProvider, 'minleech'):
-                try:
-                    curTorrentProvider.minleech = int(str(kwargs[curTorrentProvider.getID() + '_minleech']).strip())
-                except:
-                    curTorrentProvider.minleech = 0
+        # figure out what show we're adding and where
+        series_pieces = whichSeries.split('|')
+        if (whichSeries and rootDir) or (whichSeries and fullShowPath and len(series_pieces) > 1):
+            if len(series_pieces) < 6:
+                logger.log("Unable to add show due to show selection. Not anough arguments: %s" % (repr(series_pieces)),
+                           logger.ERROR)
+                ui.notifications.error("Unknown error. Unable to add show due to problem with show selection.")
+                return self.redirect('/home/addShows/existingShows/')
 
-            if hasattr(curTorrentProvider, 'ratio'):
-                try:
-                    curTorrentProvider.ratio = str(kwargs[curTorrentProvider.getID() + '_ratio']).strip()
-                except:
-                    curTorrentProvider.ratio = None
+            indexer = int(series_pieces[1])
+            indexer_id = int(series_pieces[3])
+            show_name = series_pieces[4]
+        else:
+            # if no indexer was provided use the default indexer set in General settings
+            if not providedIndexer:
+                providedIndexer = sickbeard.INDEXER_DEFAULT
 
-            if hasattr(curTorrentProvider, 'digest'):
-                try:
-                    curTorrentProvider.digest = str(kwargs[curTorrentProvider.getID() + '_digest']).strip()
-                except:
-                    curTorrentProvider.digest = None
+            indexer = int(providedIndexer)
+            indexer_id = int(whichSeries)
+            show_name = os.path.basename(os.path.normpath(fullShowPath))
 
-            if hasattr(curTorrentProvider, 'hash'):
-                try:
-                    curTorrentProvider.hash = str(kwargs[curTorrentProvider.getID() + '_hash']).strip()
-                except:
-                    curTorrentProvider.hash = None
+        # use the whole path if it's given, or else append the show name to the root dir to get the full show path
+        if fullShowPath:
+            show_dir = ek.ek(os.path.normpath, fullShowPath)
+        else:
+            show_dir = ek.ek(os.path.join, rootDir, helpers.sanitizeFileName(show_name))
 
-            if hasattr(curTorrentProvider, 'api_key'):
-                try:
-                    curTorrentProvider.api_key = str(kwargs[curTorrentProvider.getID() + '_api_key']).strip()
-                except:
-                    curTorrentProvider.api_key = None
-
-            if hasattr(curTorrentProvider, 'username'):
-                try:
-                    curTorrentProvider.username = str(kwargs[curTorrentProvider.getID() + '_username']).strip()
-                except:
-                    curTorrentProvider.username = None
-
-            if hasattr(curTorrentProvider, 'password'):
-                try:
-                    curTorrentProvider.password = str(kwargs[curTorrentProvider.getID() + '_password']).strip()
-                except:
-                    curTorrentProvider.password = None
-
-            if hasattr(curTorrentProvider, 'passkey'):
-                try:
-                    curTorrentProvider.passkey = str(kwargs[curTorrentProvider.getID() + '_passkey']).strip()
-                except:
-                    curTorrentProvider.passkey = None
-
-            if hasattr(curTorrentProvider, 'confirmed'):
-                try:
-                    curTorrentProvider.confirmed = config.checkbox_to_value(
-                        kwargs[curTorrentProvider.getID() + '_confirmed'])
-                except:
-                    curTorrentProvider.confirmed = 0
-
-            if hasattr(curTorrentProvider, 'proxy'):
-                try:
-                    curTorrentProvider.proxy.enabled = config.checkbox_to_value(
-                        kwargs[curTorrentProvider.getID() + '_proxy'])
-                except:
-                    curTorrentProvider.proxy.enabled = 0
-
-                if hasattr(curTorrentProvider.proxy, 'url'):
-                    try:
-                        curTorrentProvider.proxy.url = str(kwargs[curTorrentProvider.getID() + '_proxy_url']).strip()
-                    except:
-                        curTorrentProvider.proxy.url = None
-
-            if hasattr(curTorrentProvider, 'freeleech'):
-                try:
-                    curTorrentProvider.freeleech = config.checkbox_to_value(
-                        kwargs[curTorrentProvider.getID() + '_freeleech'])
-                except:
-                    curTorrentProvider.freeleech = 0
-
-            if hasattr(curTorrentProvider, 'search_mode'):
-                try:
-                    curTorrentProvider.search_mode = str(kwargs[curTorrentProvider.getID() + '_search_mode']).strip()
-                except:
-                    curTorrentProvider.search_mode = 'eponly'
-
-            if hasattr(curTorrentProvider, 'search_fallback'):
-                try:
-                    curTorrentProvider.search_fallback = config.checkbox_to_value(
-                        kwargs[curTorrentProvider.getID() + '_search_fallback'])
-                except:
-                    curTorrentProvider.search_fallback = 0  # these exceptions are catching unselected checkboxes
-
-            if hasattr(curTorrentProvider, 'enable_daily'):
-                try:
-                    curTorrentProvider.enable_daily = config.checkbox_to_value(
-                        kwargs[curTorrentProvider.getID() + '_enable_daily'])
-                except:
-                    curTorrentProvider.enable_daily = 0  # these exceptions are actually catching unselected checkboxes
+        # 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.notifications.error("Unable to add show", "Folder " + show_dir + " exists already")
+            return self.redirect('/home/addShows/existingShows/')
 
-            if hasattr(curTorrentProvider, 'enable_backlog'):
-                try:
-                    curTorrentProvider.enable_backlog = config.checkbox_to_value(
-                        kwargs[curTorrentProvider.getID() + '_enable_backlog'])
-                except:
-                    curTorrentProvider.enable_backlog = 0  # these exceptions are actually catching unselected checkboxes
+        # don't create show dir if config says not to
+        if sickbeard.ADD_SHOWS_WO_DIR:
+            logger.log(u"Skipping initial creation of " + show_dir + " due to config.ini setting")
+        else:
+            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.notifications.error("Unable to add show",
+                                       "Unable to create the folder " + show_dir + ", can't add the show")
+                return self.redirect("/home/")
+            else:
+                helpers.chmodAsParent(show_dir)
 
-        for curNzbProvider in [curProvider for curProvider in sickbeard.providers.sortedProviderList() if
-                               curProvider.providerType == sickbeard.GenericProvider.NZB]:
+        # prepare the inputs for passing along
+        scene = config.checkbox_to_value(scene)
+        anime = config.checkbox_to_value(anime)
+        flatten_folders = config.checkbox_to_value(flatten_folders)
+        subtitles = config.checkbox_to_value(subtitles)
 
-            if hasattr(curNzbProvider, 'api_key'):
-                try:
-                    curNzbProvider.api_key = str(kwargs[curNzbProvider.getID() + '_api_key']).strip()
-                except:
-                    curNzbProvider.api_key = None
+        if not anyQualities:
+            anyQualities = []
+        if not bestQualities:
+            bestQualities = []
+        if type(anyQualities) != list:
+            anyQualities = [anyQualities]
+        if type(bestQualities) != list:
+            bestQualities = [bestQualities]
+        newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
 
-            if hasattr(curNzbProvider, 'username'):
-                try:
-                    curNzbProvider.username = str(kwargs[curNzbProvider.getID() + '_username']).strip()
-                except:
-                    curNzbProvider.username = None
+        # add the show
+        sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality,
+                                                    flatten_folders, indexerLang, subtitles, anime,
+                                                    scene)
+        ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
 
-            if hasattr(curNzbProvider, 'search_mode'):
-                try:
-                    curNzbProvider.search_mode = str(kwargs[curNzbProvider.getID() + '_search_mode']).strip()
-                except:
-                    curNzbProvider.search_mode = 'eponly'
+        return finishAddShow()
 
-            if hasattr(curNzbProvider, 'search_fallback'):
-                try:
-                    curNzbProvider.search_fallback = config.checkbox_to_value(
-                        kwargs[curNzbProvider.getID() + '_search_fallback'])
-                except:
-                    curNzbProvider.search_fallback = 0  # these exceptions are actually catching unselected checkboxes
+    def split_extra_show(self, extra_show):
+        if not extra_show:
+            return (None, None, None, None)
+        split_vals = extra_show.split('|')
+        if len(split_vals) < 4:
+            indexer = split_vals[0]
+            show_dir = split_vals[1]
+            return (indexer, show_dir, None, None)
+        indexer = split_vals[0]
+        show_dir = split_vals[1]
+        indexer_id = split_vals[2]
+        show_name = '|'.join(split_vals[3:])
 
-            if hasattr(curNzbProvider, 'enable_daily'):
-                try:
-                    curNzbProvider.enable_daily = config.checkbox_to_value(
-                        kwargs[curNzbProvider.getID() + '_enable_daily'])
-                except:
-                    curNzbProvider.enable_daily = 0  # these exceptions are actually catching unselected checkboxes
+        return (indexer, show_dir, indexer_id, show_name)
 
-            if hasattr(curNzbProvider, 'enable_backlog'):
-                try:
-                    curNzbProvider.enable_backlog = config.checkbox_to_value(
-                        kwargs[curNzbProvider.getID() + '_enable_backlog'])
-                except:
-                    curNzbProvider.enable_backlog = 0  # these exceptions are actually catching unselected checkboxes
 
-        sickbeard.NEWZNAB_DATA = '!!!'.join([x.configStr() for x in sickbeard.newznabProviderList])
-        sickbeard.PROVIDER_ORDER = provider_list
+    def addExistingShows(self, shows_to_add=None, promptForSettings=None):
+        """
+        Receives a dir list and add them. Adds the ones with given TVDB IDs first, then forwards
+        along to the newShow page.
+        """
 
-        sickbeard.save_config()
+        # grab a list of other shows to add, if provided
+        if not shows_to_add:
+            shows_to_add = []
+        elif type(shows_to_add) != list:
+            shows_to_add = [shows_to_add]
 
-        if len(results) > 0:
-            for x in results:
-                logger.log(x, logger.ERROR)
-            ui.notifications.error('Error(s) Saving Configuration',
-                                   '<br />\n'.join(results))
-        else:
-            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
+        shows_to_add = [urllib.unquote_plus(x) for x in shows_to_add]
 
-        redirect("/config/providers/")
+        promptForSettings = config.checkbox_to_value(promptForSettings)
 
+        indexer_id_given = []
+        dirs_only = []
+        # separate all the ones with Indexer IDs
+        for cur_dir in shows_to_add:
+            if '|' in cur_dir:
+                split_vals = cur_dir.split('|')
+                if len(split_vals) < 3:
+                    dirs_only.append(cur_dir)
+            if not '|' in cur_dir:
+                dirs_only.append(cur_dir)
+            else:
+                indexer, show_dir, indexer_id, show_name = self.split_extra_show(cur_dir)
 
-class ConfigNotifications(MainHandler):
-    def index(self, *args, **kwargs):
-        t = PageTemplate(headers=self.request.headers, file="config_notifications.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+                if not show_dir or not indexer_id or not show_name:
+                    continue
 
+                indexer_id_given.append((int(indexer), show_dir, int(indexer_id), show_name))
 
-    def saveNotifications(self, use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None,
-                          xbmc_notify_ondownload=None,
-                          xbmc_notify_onsubtitledownload=None, xbmc_update_onlyfirst=None,
-                          xbmc_update_library=None, xbmc_update_full=None, xbmc_host=None, xbmc_username=None,
-                          xbmc_password=None,
-                          use_plex=None, plex_notify_onsnatch=None, plex_notify_ondownload=None,
-                          plex_notify_onsubtitledownload=None, plex_update_library=None,
-                          plex_server_host=None, plex_host=None, plex_username=None, plex_password=None,
-                          use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None,
-                          growl_notify_onsubtitledownload=None, growl_host=None, growl_password=None,
-                          use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None,
-                          prowl_notify_onsubtitledownload=None, prowl_api=None, prowl_priority=0,
-                          use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None,
-                          twitter_notify_onsubtitledownload=None,
-                          use_boxcar=None, boxcar_notify_onsnatch=None, boxcar_notify_ondownload=None,
-                          boxcar_notify_onsubtitledownload=None, boxcar_username=None,
-                          use_boxcar2=None, boxcar2_notify_onsnatch=None, boxcar2_notify_ondownload=None,
-                          boxcar2_notify_onsubtitledownload=None, boxcar2_accesstoken=None,
-                          use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None,
-                          pushover_notify_onsubtitledownload=None, pushover_userkey=None, pushover_apikey=None,
-                          use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None,
-                          libnotify_notify_onsubtitledownload=None,
-                          use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None,
-                          use_nmjv2=None, nmjv2_host=None, nmjv2_dbloc=None, nmjv2_database=None,
-                          use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None,
-                          trakt_remove_watchlist=None, trakt_use_watchlist=None, trakt_method_add=None,
-                          trakt_start_paused=None, trakt_use_recommended=None, trakt_sync=None,
-                          trakt_default_indexer=None, trakt_remove_serieslist=None,
-                          use_synologynotifier=None, synologynotifier_notify_onsnatch=None,
-                          synologynotifier_notify_ondownload=None, synologynotifier_notify_onsubtitledownload=None,
-                          use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None,
-                          pytivo_notify_onsubtitledownload=None, pytivo_update_library=None,
-                          pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None,
-                          use_nma=None, nma_notify_onsnatch=None, nma_notify_ondownload=None,
-                          nma_notify_onsubtitledownload=None, nma_api=None, nma_priority=0,
-                          use_pushalot=None, pushalot_notify_onsnatch=None, pushalot_notify_ondownload=None,
-                          pushalot_notify_onsubtitledownload=None, pushalot_authorizationtoken=None,
-                          use_pushbullet=None, pushbullet_notify_onsnatch=None, pushbullet_notify_ondownload=None,
-                          pushbullet_notify_onsubtitledownload=None, pushbullet_api=None, pushbullet_device=None,
-                          pushbullet_device_list=None,
-                          use_email=None, email_notify_onsnatch=None, email_notify_ondownload=None,
-                          email_notify_onsubtitledownload=None, email_host=None, email_port=25, email_from=None,
-                          email_tls=None, email_user=None, email_password=None, email_list=None, email_show_list=None,
-                          email_show=None):
 
-        results = []
+        # if they want me to prompt for settings then I will just carry on to the newShow page
+        if promptForSettings and shows_to_add:
+            return self.newShow(shows_to_add[0], shows_to_add[1:])
 
-        sickbeard.USE_XBMC = config.checkbox_to_value(use_xbmc)
-        sickbeard.XBMC_ALWAYS_ON = config.checkbox_to_value(xbmc_always_on)
-        sickbeard.XBMC_NOTIFY_ONSNATCH = config.checkbox_to_value(xbmc_notify_onsnatch)
-        sickbeard.XBMC_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(xbmc_notify_ondownload)
-        sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(xbmc_notify_onsubtitledownload)
-        sickbeard.XBMC_UPDATE_LIBRARY = config.checkbox_to_value(xbmc_update_library)
-        sickbeard.XBMC_UPDATE_FULL = config.checkbox_to_value(xbmc_update_full)
-        sickbeard.XBMC_UPDATE_ONLYFIRST = config.checkbox_to_value(xbmc_update_onlyfirst)
-        sickbeard.XBMC_HOST = config.clean_hosts(xbmc_host)
-        sickbeard.XBMC_USERNAME = xbmc_username
-        sickbeard.XBMC_PASSWORD = xbmc_password
+        # if they don't want me to prompt for settings then I can just add all the nfo shows now
+        num_added = 0
+        for cur_show in indexer_id_given:
+            indexer, show_dir, indexer_id, show_name = cur_show
 
-        sickbeard.USE_PLEX = config.checkbox_to_value(use_plex)
-        sickbeard.PLEX_NOTIFY_ONSNATCH = config.checkbox_to_value(plex_notify_onsnatch)
-        sickbeard.PLEX_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(plex_notify_ondownload)
-        sickbeard.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(plex_notify_onsubtitledownload)
-        sickbeard.PLEX_UPDATE_LIBRARY = config.checkbox_to_value(plex_update_library)
-        sickbeard.PLEX_HOST = config.clean_hosts(plex_host)
-        sickbeard.PLEX_SERVER_HOST = config.clean_host(plex_server_host)
-        sickbeard.PLEX_USERNAME = plex_username
-        sickbeard.PLEX_PASSWORD = plex_password
+            if indexer is not None and indexer_id is not None:
+                # add the show
+                sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir,
+                                                            default_status=sickbeard.STATUS_DEFAULT,
+                                                            quality=sickbeard.QUALITY_DEFAULT,
+                                                            flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT,
+                                                            subtitles=sickbeard.SUBTITLES_DEFAULT,
+                                                            anime=sickbeard.ANIME_DEFAULT,
+                                                            scene=sickbeard.SCENE_DEFAULT)
+                num_added += 1
 
-        sickbeard.USE_GROWL = config.checkbox_to_value(use_growl)
-        sickbeard.GROWL_NOTIFY_ONSNATCH = config.checkbox_to_value(growl_notify_onsnatch)
-        sickbeard.GROWL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(growl_notify_ondownload)
-        sickbeard.GROWL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(growl_notify_onsubtitledownload)
-        sickbeard.GROWL_HOST = config.clean_host(growl_host, default_port=23053)
-        sickbeard.GROWL_PASSWORD = growl_password
+        if num_added:
+            ui.notifications.message("Shows Added",
+                                     "Automatically added " + str(num_added) + " from their existing metadata files")
 
-        sickbeard.USE_PROWL = config.checkbox_to_value(use_prowl)
-        sickbeard.PROWL_NOTIFY_ONSNATCH = config.checkbox_to_value(prowl_notify_onsnatch)
-        sickbeard.PROWL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(prowl_notify_ondownload)
-        sickbeard.PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(prowl_notify_onsubtitledownload)
-        sickbeard.PROWL_API = prowl_api
-        sickbeard.PROWL_PRIORITY = prowl_priority
+        # if we're done then go home
+        if not dirs_only:
+            return self.redirect('/home/')
 
-        sickbeard.USE_TWITTER = config.checkbox_to_value(use_twitter)
-        sickbeard.TWITTER_NOTIFY_ONSNATCH = config.checkbox_to_value(twitter_notify_onsnatch)
-        sickbeard.TWITTER_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(twitter_notify_ondownload)
-        sickbeard.TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(twitter_notify_onsubtitledownload)
+        # for the remaining shows we need to prompt for each one, so forward this on to the newShow page
+        return self.newShow(dirs_only[0], dirs_only[1:])
 
-        sickbeard.USE_BOXCAR = config.checkbox_to_value(use_boxcar)
-        sickbeard.BOXCAR_NOTIFY_ONSNATCH = config.checkbox_to_value(boxcar_notify_onsnatch)
-        sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(boxcar_notify_ondownload)
-        sickbeard.BOXCAR_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(boxcar_notify_onsubtitledownload)
-        sickbeard.BOXCAR_USERNAME = boxcar_username
 
-        sickbeard.USE_BOXCAR2 = config.checkbox_to_value(use_boxcar2)
-        sickbeard.BOXCAR2_NOTIFY_ONSNATCH = config.checkbox_to_value(boxcar2_notify_onsnatch)
-        sickbeard.BOXCAR2_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(boxcar2_notify_ondownload)
-        sickbeard.BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(boxcar2_notify_onsubtitledownload)
-        sickbeard.BOXCAR2_ACCESSTOKEN = boxcar2_accesstoken
+@route('/manage(/?.*)')
+class Manage(Home, WebRoot):
+    def __init__(self, *args, **kwargs):
+        super(Manage, self).__init__(*args, **kwargs)
 
-        sickbeard.USE_PUSHOVER = config.checkbox_to_value(use_pushover)
-        sickbeard.PUSHOVER_NOTIFY_ONSNATCH = config.checkbox_to_value(pushover_notify_onsnatch)
-        sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushover_notify_ondownload)
-        sickbeard.PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushover_notify_onsubtitledownload)
-        sickbeard.PUSHOVER_USERKEY = pushover_userkey
-        sickbeard.PUSHOVER_APIKEY = pushover_apikey
+    def ManageMenu(self):
+        menu = [
+            {'title': 'Backlog Overview', 'path': 'manage/backlogOverview/'},
+            {'title': 'Manage Searches', 'path': 'manage/manageSearches/'},
+            {'title': 'Episode Status Management', 'path': 'manage/episodeStatuses/'}, ]
 
-        sickbeard.USE_LIBNOTIFY = config.checkbox_to_value(use_libnotify)
-        sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH = config.checkbox_to_value(libnotify_notify_onsnatch)
-        sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(libnotify_notify_ondownload)
-        sickbeard.LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(libnotify_notify_onsubtitledownload)
+        if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \
+                and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https'
+                     or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'):
+            menu.append({'title': 'Manage Torrents', 'path': 'manage/manageTorrents/'})
 
-        sickbeard.USE_NMJ = config.checkbox_to_value(use_nmj)
-        sickbeard.NMJ_HOST = config.clean_host(nmj_host)
-        sickbeard.NMJ_DATABASE = nmj_database
-        sickbeard.NMJ_MOUNT = nmj_mount
+        if sickbeard.USE_SUBTITLES:
+            menu.append({'title': 'Missed Subtitle Management', 'path': 'manage/subtitleMissed/'})
 
-        sickbeard.USE_NMJv2 = config.checkbox_to_value(use_nmjv2)
-        sickbeard.NMJv2_HOST = config.clean_host(nmjv2_host)
-        sickbeard.NMJv2_DATABASE = nmjv2_database
-        sickbeard.NMJv2_DBLOC = nmjv2_dbloc
+        if sickbeard.USE_FAILED_DOWNLOADS:
+            menu.append({'title': 'Failed Downloads', 'path': 'manage/failedDownloads/'})
 
-        sickbeard.USE_SYNOINDEX = config.checkbox_to_value(use_synoindex)
+        return menu
 
-        sickbeard.USE_SYNOLOGYNOTIFIER = config.checkbox_to_value(use_synologynotifier)
-        sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH = config.checkbox_to_value(synologynotifier_notify_onsnatch)
-        sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(synologynotifier_notify_ondownload)
-        sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(
-            synologynotifier_notify_onsubtitledownload)
+    def index(self):
+        t = PageTemplate(rh=self, file="manage.tmpl")
+        t.submenu = self.ManageMenu()
+        return t.respond()
 
-        sickbeard.USE_TRAKT = config.checkbox_to_value(use_trakt)
-        sickbeard.TRAKT_USERNAME = trakt_username
-        sickbeard.TRAKT_PASSWORD = trakt_password
-        sickbeard.TRAKT_API = trakt_api
-        sickbeard.TRAKT_REMOVE_WATCHLIST = config.checkbox_to_value(trakt_remove_watchlist)
-        sickbeard.TRAKT_REMOVE_SERIESLIST = config.checkbox_to_value(trakt_remove_serieslist)
-        sickbeard.TRAKT_USE_WATCHLIST = config.checkbox_to_value(trakt_use_watchlist)
-        sickbeard.TRAKT_METHOD_ADD = int(trakt_method_add)
-        sickbeard.TRAKT_START_PAUSED = config.checkbox_to_value(trakt_start_paused)
-        sickbeard.TRAKT_USE_RECOMMENDED = config.checkbox_to_value(trakt_use_recommended)
-        sickbeard.TRAKT_SYNC = config.checkbox_to_value(trakt_sync)
-        sickbeard.TRAKT_DEFAULT_INDEXER = int(trakt_default_indexer)
 
-        if sickbeard.USE_TRAKT:
-            sickbeard.traktCheckerScheduler.silent = False
-        else:
-            sickbeard.traktCheckerScheduler.silent = True
+    def showEpisodeStatuses(self, indexer_id, whichStatus):
+        status_list = [int(whichStatus)]
+        if status_list[0] == SNATCHED:
+            status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
 
-        sickbeard.USE_EMAIL = config.checkbox_to_value(use_email)
-        sickbeard.EMAIL_NOTIFY_ONSNATCH = config.checkbox_to_value(email_notify_onsnatch)
-        sickbeard.EMAIL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(email_notify_ondownload)
-        sickbeard.EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(email_notify_onsubtitledownload)
-        sickbeard.EMAIL_HOST = config.clean_host(email_host)
-        sickbeard.EMAIL_PORT = config.to_int(email_port, default=25)
-        sickbeard.EMAIL_FROM = email_from
-        sickbeard.EMAIL_TLS = config.checkbox_to_value(email_tls)
-        sickbeard.EMAIL_USER = email_user
-        sickbeard.EMAIL_PASSWORD = email_password
-        sickbeard.EMAIL_LIST = email_list
+        myDB = db.DBConnection()
+        cur_show_results = myDB.select(
+            "SELECT season, episode, name FROM tv_episodes WHERE showid = ? AND season != 0 AND status IN (" + ','.join(
+                ['?'] * len(status_list)) + ")", [int(indexer_id)] + status_list)
 
-        sickbeard.USE_PYTIVO = config.checkbox_to_value(use_pytivo)
-        sickbeard.PYTIVO_NOTIFY_ONSNATCH = config.checkbox_to_value(pytivo_notify_onsnatch)
-        sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pytivo_notify_ondownload)
-        sickbeard.PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pytivo_notify_onsubtitledownload)
-        sickbeard.PYTIVO_UPDATE_LIBRARY = config.checkbox_to_value(pytivo_update_library)
-        sickbeard.PYTIVO_HOST = config.clean_host(pytivo_host)
-        sickbeard.PYTIVO_SHARE_NAME = pytivo_share_name
-        sickbeard.PYTIVO_TIVO_NAME = pytivo_tivo_name
+        result = {}
+        for cur_result in cur_show_results:
+            cur_season = int(cur_result["season"])
+            cur_episode = int(cur_result["episode"])
 
-        sickbeard.USE_NMA = config.checkbox_to_value(use_nma)
-        sickbeard.NMA_NOTIFY_ONSNATCH = config.checkbox_to_value(nma_notify_onsnatch)
-        sickbeard.NMA_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(nma_notify_ondownload)
-        sickbeard.NMA_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(nma_notify_onsubtitledownload)
-        sickbeard.NMA_API = nma_api
-        sickbeard.NMA_PRIORITY = nma_priority
+            if cur_season not in result:
+                result[cur_season] = {}
 
-        sickbeard.USE_PUSHALOT = config.checkbox_to_value(use_pushalot)
-        sickbeard.PUSHALOT_NOTIFY_ONSNATCH = config.checkbox_to_value(pushalot_notify_onsnatch)
-        sickbeard.PUSHALOT_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushalot_notify_ondownload)
-        sickbeard.PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushalot_notify_onsubtitledownload)
-        sickbeard.PUSHALOT_AUTHORIZATIONTOKEN = pushalot_authorizationtoken
+            result[cur_season][cur_episode] = cur_result["name"]
 
-        sickbeard.USE_PUSHBULLET = config.checkbox_to_value(use_pushbullet)
-        sickbeard.PUSHBULLET_NOTIFY_ONSNATCH = config.checkbox_to_value(pushbullet_notify_onsnatch)
-        sickbeard.PUSHBULLET_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushbullet_notify_ondownload)
-        sickbeard.PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushbullet_notify_onsubtitledownload)
-        sickbeard.PUSHBULLET_API = pushbullet_api
-        sickbeard.PUSHBULLET_DEVICE = pushbullet_device_list
+        return json.dumps(result)
 
-        sickbeard.save_config()
 
-        if len(results) > 0:
-            for x in results:
-                logger.log(x, logger.ERROR)
-            ui.notifications.error('Error(s) Saving Configuration',
-                                   '<br />\n'.join(results))
+    def episodeStatuses(self, whichStatus=None):
+        if whichStatus:
+            whichStatus = int(whichStatus)
+            status_list = [whichStatus]
+            if status_list[0] == SNATCHED:
+                status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
         else:
-            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
+            status_list = []
 
-        redirect("/config/notifications/")
+        t = PageTemplate(rh=self, file="manage_episodeStatuses.tmpl")
+        t.submenu = self.ManageMenu()
+        t.whichStatus = whichStatus
 
+        # if we have no status then this is as far as we need to go
+        if not status_list:
+            return t.respond()
 
-class ConfigSubtitles(MainHandler):
-    def index(self, *args, **kwargs):
-        t = PageTemplate(headers=self.request.headers, file="config_subtitles.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+        myDB = db.DBConnection()
+        status_results = myDB.select(
+            "SELECT show_name, tv_shows.indexer_id AS indexer_id FROM tv_episodes, tv_shows WHERE tv_episodes.status IN (" + ','.join(
+                ['?'] * len(
+                    status_list)) + ") AND season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name",
+            status_list)
 
+        ep_counts = {}
+        show_names = {}
+        sorted_show_ids = []
+        for cur_status_result in status_results:
+            cur_indexer_id = int(cur_status_result["indexer_id"])
+            if cur_indexer_id not in ep_counts:
+                ep_counts[cur_indexer_id] = 1
+            else:
+                ep_counts[cur_indexer_id] += 1
 
-    def saveSubtitles(self, use_subtitles=None, subtitles_plugins=None, subtitles_languages=None, subtitles_dir=None,
-                      service_order=None, subtitles_history=None, subtitles_finder_frequency=None):
-        results = []
+            show_names[cur_indexer_id] = cur_status_result["show_name"]
+            if cur_indexer_id not in sorted_show_ids:
+                sorted_show_ids.append(cur_indexer_id)
 
-        if subtitles_finder_frequency == '' or subtitles_finder_frequency is None:
-            subtitles_finder_frequency = 1
+        t.show_names = show_names
+        t.ep_counts = ep_counts
+        t.sorted_show_ids = sorted_show_ids
+        return t.respond()
 
-        if use_subtitles == "on" and not sickbeard.subtitlesFinderScheduler.isAlive():
-            sickbeard.subtitlesFinderScheduler.silent = False
-            try:
-                sickbeard.subtitlesFinderScheduler.start()
-            except:
-                pass
-        elif not use_subtitles == "on":
-            sickbeard.subtitlesFinderScheduler.stop.set()
-            sickbeard.subtitlesFinderScheduler.silent = True
-            try:
-                sickbeard.subtitlesFinderScheduler.join(5)
-            except:
-                pass
 
-        sickbeard.USE_SUBTITLES = config.checkbox_to_value(use_subtitles)
-        sickbeard.SUBTITLES_LANGUAGES = [lang.alpha2 for lang in subtitles.isValidLanguage(
-            subtitles_languages.replace(' ', '').split(','))] if subtitles_languages != '' else ''
-        sickbeard.SUBTITLES_DIR = subtitles_dir
-        sickbeard.SUBTITLES_HISTORY = config.checkbox_to_value(subtitles_history)
-        sickbeard.SUBTITLES_FINDER_FREQUENCY = config.to_int(subtitles_finder_frequency, default=1)
+    def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs):
 
-        # Subtitles services
-        services_str_list = service_order.split()
-        subtitles_services_list = []
-        subtitles_services_enabled = []
-        for curServiceStr in services_str_list:
-            curService, curEnabled = curServiceStr.split(':')
-            subtitles_services_list.append(curService)
-            subtitles_services_enabled.append(int(curEnabled))
+        status_list = [int(oldStatus)]
+        if status_list[0] == SNATCHED:
+            status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
 
-        sickbeard.SUBTITLES_SERVICES_LIST = subtitles_services_list
-        sickbeard.SUBTITLES_SERVICES_ENABLED = subtitles_services_enabled
+        to_change = {}
 
-        sickbeard.save_config()
+        # make a list of all shows and their associated args
+        for arg in kwargs:
+            indexer_id, what = arg.split('-')
 
-        if len(results) > 0:
-            for x in results:
-                logger.log(x, logger.ERROR)
-            ui.notifications.error('Error(s) Saving Configuration',
-                                   '<br />\n'.join(results))
-        else:
-            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
+            # we don't care about unchecked checkboxes
+            if kwargs[arg] != 'on':
+                continue
 
-        redirect("/config/subtitles/")
+            if indexer_id not in to_change:
+                to_change[indexer_id] = []
 
+            to_change[indexer_id].append(what)
 
-class ConfigAnime(MainHandler):
-    def index(self, *args, **kwargs):
+        myDB = db.DBConnection()
+        for cur_indexer_id in to_change:
 
-        t = PageTemplate(headers=self.request.headers, file="config_anime.tmpl")
-        t.submenu = ConfigMenu
-        return _munge(t)
+            # get a list of all the eps we want to change if they just said "all"
+            if 'all' in to_change[cur_indexer_id]:
+                all_eps_results = myDB.select(
+                    "SELECT season, episode FROM tv_episodes WHERE status IN (" + ','.join(
+                        ['?'] * len(status_list)) + ") AND season != 0 AND showid = ?",
+                    status_list + [cur_indexer_id])
+                all_eps = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results]
+                to_change[cur_indexer_id] = all_eps
 
+            self.setStatus(cur_indexer_id, '|'.join(to_change[cur_indexer_id]), newStatus, direct=True)
 
-    def saveAnime(self, use_anidb=None, anidb_username=None, anidb_password=None, anidb_use_mylist=None,
-                  split_home=None):
+        return self.redirect('/manage/episodeStatuses/')
 
-        results = []
 
-        sickbeard.USE_ANIDB = config.checkbox_to_value(use_anidb)
-        sickbeard.ANIDB_USERNAME = anidb_username
-        sickbeard.ANIDB_PASSWORD = anidb_password
-        sickbeard.ANIDB_USE_MYLIST = config.checkbox_to_value(anidb_use_mylist)
-        sickbeard.ANIME_SPLIT_HOME = config.checkbox_to_value(split_home)
+    def showSubtitleMissed(self, indexer_id, whichSubs):
+        myDB = db.DBConnection()
+        cur_show_results = myDB.select(
+            "SELECT season, episode, name, subtitles FROM tv_episodes WHERE showid = ? AND season != 0 AND status LIKE '%4'",
+            [int(indexer_id)])
+
+        result = {}
+        for cur_result in cur_show_results:
+            if whichSubs == 'all':
+                if len(set(cur_result["subtitles"].split(',')).intersection(set(subtitles.wantedLanguages()))) >= len(
+                        subtitles.wantedLanguages()):
+                    continue
+            elif whichSubs in cur_result["subtitles"].split(','):
+                continue
+
+            cur_season = int(cur_result["season"])
+            cur_episode = int(cur_result["episode"])
+
+            if cur_season not in result:
+                result[cur_season] = {}
+
+            if cur_episode not in result[cur_season]:
+                result[cur_season][cur_episode] = {}
+
+            result[cur_season][cur_episode]["name"] = cur_result["name"]
+
+            result[cur_season][cur_episode]["subtitles"] = ",".join(
+                subliminal.language.Language(subtitle).alpha2 for subtitle in cur_result["subtitles"].split(',')) if not \
+                cur_result["subtitles"] == '' else ''
 
-        sickbeard.save_config()
+        return json.dumps(result)
 
-        if len(results) > 0:
-            for x in results:
-                logger.log(x, logger.ERROR)
-            ui.notifications.error('Error(s) Saving Configuration',
-                                   '<br />\n'.join(results))
-        else:
-            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
-        redirect("/config/anime/")
+    def subtitleMissed(self, whichSubs=None):
 
+        t = PageTemplate(rh=self, file="manage_subtitleMissed.tmpl")
+        t.submenu = self.ManageMenu()
+        t.whichSubs = whichSubs
 
-class Config(MainHandler):
-    def index(self, *args, **kwargs):
-        t = PageTemplate(headers=self.request.headers, file="config.tmpl")
-        t.submenu = ConfigMenu
+        if not whichSubs:
+            return t.respond()
 
-        return _munge(t)
+        myDB = db.DBConnection()
+        status_results = myDB.select(
+            "SELECT show_name, tv_shows.indexer_id as indexer_id, tv_episodes.subtitles subtitles FROM tv_episodes, tv_shows WHERE tv_shows.subtitles = 1 AND tv_episodes.status LIKE '%4' AND tv_episodes.season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name")
 
-    # map class names to urls
-    general = ConfigGeneral
-    backuprestore = ConfigBackupRestore
-    search = ConfigSearch
-    providers = ConfigProviders
-    subtitles = ConfigSubtitles
-    postProcessing = ConfigPostProcessing
-    notifications = ConfigNotifications
-    anime = ConfigAnime
+        ep_counts = {}
+        show_names = {}
+        sorted_show_ids = []
+        for cur_status_result in status_results:
+            if whichSubs == 'all':
+                if len(set(cur_status_result["subtitles"].split(',')).intersection(
+                        set(subtitles.wantedLanguages()))) >= len(subtitles.wantedLanguages()):
+                    continue
+            elif whichSubs in cur_status_result["subtitles"].split(','):
+                continue
 
+            cur_indexer_id = int(cur_status_result["indexer_id"])
+            if cur_indexer_id not in ep_counts:
+                ep_counts[cur_indexer_id] = 1
+            else:
+                ep_counts[cur_indexer_id] += 1
 
-def haveXBMC():
-    return sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY
+            show_names[cur_indexer_id] = cur_status_result["show_name"]
+            if cur_indexer_id not in sorted_show_ids:
+                sorted_show_ids.append(cur_indexer_id)
 
+        t.show_names = show_names
+        t.ep_counts = ep_counts
+        t.sorted_show_ids = sorted_show_ids
+        return t.respond()
 
-def havePLEX():
-    return sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY
 
+    def downloadSubtitleMissed(self, *args, **kwargs):
 
-def haveTORRENT():
-    if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \
-            and (sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'https'
-                 or not sickbeard.ENABLE_HTTPS and sickbeard.TORRENT_HOST[:5] == 'http:'):
-        return True
-    else:
-        return False
+        to_download = {}
 
+        # make a list of all shows and their associated args
+        for arg in kwargs:
+            indexer_id, what = arg.split('-')
 
-def HomeMenu():
-    return [
-        {'title': 'Add Shows', 'path': 'home/addShows/', },
-        {'title': 'Manual Post-Processing', 'path': 'home/postprocess/'},
-        {'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': haveXBMC},
-        {'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': havePLEX},
-        {'title': 'Manage Torrents', 'path': 'manage/manageTorrents', 'requires': haveTORRENT},
-        {'title': 'Restart', 'path': 'home/restart/?pid=' + str(sickbeard.PID), 'confirm': True},
-        {'title': 'Shutdown', 'path': 'home/shutdown/?pid=' + str(sickbeard.PID), 'confirm': True},
-    ]
+            # we don't care about unchecked checkboxes
+            if kwargs[arg] != 'on':
+                continue
 
+            if indexer_id not in to_download:
+                to_download[indexer_id] = []
 
-class HomePostProcess(MainHandler):
-    def index(self, *args, **kwargs):
+            to_download[indexer_id].append(what)
 
-        t = PageTemplate(headers=self.request.headers, file="home_postprocess.tmpl")
-        t.submenu = HomeMenu()
-        return _munge(t)
+        for cur_indexer_id in to_download:
+            # get a list of all the eps we want to download subtitles if they just said "all"
+            if 'all' in to_download[cur_indexer_id]:
+                myDB = db.DBConnection()
+                all_eps_results = myDB.select(
+                    "SELECT season, episode FROM tv_episodes WHERE status LIKE '%4' AND season != 0 AND showid = ?",
+                    [cur_indexer_id])
+                to_download[cur_indexer_id] = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results]
 
-    def processEpisode(self, dir=None, nzbName=None, jobName=None, quiet=None, process_method=None, force=None,
-                       is_priority=None, failed="0", type="auto", *args, **kwargs):
+            for epResult in to_download[cur_indexer_id]:
+                season, episode = epResult.split('x')
 
-        if failed == "0":
-            failed = False
-        else:
-            failed = True
+                show = sickbeard.helpers.findCertainShow(sickbeard.showList, int(cur_indexer_id))
+                subtitles = show.getEpisode(int(season), int(episode)).downloadSubtitles()
 
-        if force in ["on", "1"]:
-            force = True
-        else:
-            force = False
+        return self.redirect('/manage/subtitleMissed/')
 
-        if is_priority in ["on", "1"]:
-            is_priority = True
-        else:
-            is_priority = False
 
-        if not dir:
-            redirect("/home/postprocess/")
-        else:
-            result = processTV.processDir(dir, nzbName, process_method=process_method, force=force,
-                                          is_priority=is_priority, failed=failed, type=type)
-            if quiet is not None and int(quiet) == 1:
-                return result
+    def backlogShow(self, indexer_id):
 
-            result = result.replace("\n", "<br />\n")
-            return self._genericMessage("Postprocessing results", result)
+        show_obj = helpers.findCertainShow(sickbeard.showList, int(indexer_id))
 
+        if show_obj:
+            sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj])
 
-class NewHomeAddShows(MainHandler):
-    def index(self, *args, **kwargs):
+        return self.redirect("/manage/backlogOverview/")
 
-        t = PageTemplate(headers=self.request.headers, file="home_addShows.tmpl")
-        t.submenu = HomeMenu()
-        return _munge(t)
 
+    def backlogOverview(self):
 
-    def getIndexerLanguages(self, *args, **kwargs):
-        result = sickbeard.indexerApi().config['valid_languages']
+        t = PageTemplate(rh=self, file="manage_backlogOverview.tmpl")
+        t.submenu = self.ManageMenu()
 
-        # Make sure list is sorted alphabetically but 'en' is in front
-        if 'en' in result:
-            del result[result.index('en')]
-        result.sort()
-        result.insert(0, 'en')
+        showCounts = {}
+        showCats = {}
+        showSQLResults = {}
 
-        return json.dumps({'results': result})
+        myDB = db.DBConnection()
+        for curShow in sickbeard.showList:
 
+            epCounts = {}
+            epCats = {}
+            epCounts[Overview.SKIPPED] = 0
+            epCounts[Overview.WANTED] = 0
+            epCounts[Overview.QUAL] = 0
+            epCounts[Overview.GOOD] = 0
+            epCounts[Overview.UNAIRED] = 0
+            epCounts[Overview.SNATCHED] = 0
 
-    def sanitizeFileName(self, name):
-        return helpers.sanitizeFileName(name)
+            sqlResults = myDB.select(
+                "SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC",
+                [curShow.indexerid])
 
+            for curResult in sqlResults:
+                curEpCat = curShow.getOverview(int(curResult["status"] or -1))
+                if curEpCat:
+                    epCats[str(curResult["season"]) + "x" + str(curResult["episode"])] = curEpCat
+                    epCounts[curEpCat] += 1
 
-    def searchIndexersForShowName(self, search_term, lang="en", indexer=None):
-        if not lang or lang == 'null':
-            lang = "en"
+            showCounts[curShow.indexerid] = epCounts
+            showCats[curShow.indexerid] = epCats
+            showSQLResults[curShow.indexerid] = sqlResults
 
-        search_term = search_term.encode('utf-8')
+        t.showCounts = showCounts
+        t.showCats = showCats
+        t.showSQLResults = showSQLResults
 
-        results = {}
-        final_results = []
+        return t.respond()
 
-        # Query Indexers for each search term and build the list of results
-        for indexer in sickbeard.indexerApi().indexers if not int(indexer) else [int(indexer)]:
-            lINDEXER_API_PARMS = sickbeard.indexerApi(indexer).api_params.copy()
-            lINDEXER_API_PARMS['language'] = lang
-            lINDEXER_API_PARMS['custom_ui'] = classes.AllShowsListUI
-            t = sickbeard.indexerApi(indexer).indexer(**lINDEXER_API_PARMS)
 
-            logger.log("Searching for Show with searchterm: %s on Indexer: %s" % (
-                search_term, sickbeard.indexerApi(indexer).name), logger.DEBUG)
-            try:
-                # add search results
-                results.setdefault(indexer, []).extend(t[search_term])
-            except Exception, e:
-                continue
+    def massEdit(self, toEdit=None):
 
-        map(final_results.extend,
-            ([[sickbeard.indexerApi(id).name, id, sickbeard.indexerApi(id).config["show_url"], int(show['id']),
-               show['seriesname'], show['firstaired']] for show in shows] for id, shows in results.items()))
+        t = PageTemplate(rh=self, file="manage_massEdit.tmpl")
+        t.submenu = self.ManageMenu()
 
-        lang_id = sickbeard.indexerApi().config['langabbv_to_id'][lang]
-        return json.dumps({'results': final_results, 'langid': lang_id})
+        if not toEdit:
+            return self.redirect("/manage/")
 
+        showIDs = toEdit.split("|")
+        showList = []
+        for curID in showIDs:
+            curID = int(curID)
+            showObj = helpers.findCertainShow(sickbeard.showList, curID)
+            if showObj:
+                showList.append(showObj)
 
-    def massAddTable(self, rootDir=None):
-        t = PageTemplate(headers=self.request.headers, file="home_massAddTable.tmpl")
-        t.submenu = HomeMenu()
+        archive_firstmatch_all_same = True
+        last_archive_firstmatch = None
 
-        if not rootDir:
-            return "No folders selected."
-        elif type(rootDir) != list:
-            root_dirs = [rootDir]
-        else:
-            root_dirs = rootDir
+        flatten_folders_all_same = True
+        last_flatten_folders = None
 
-        root_dirs = [urllib.unquote_plus(x) for x in root_dirs]
+        paused_all_same = True
+        last_paused = None
 
-        if sickbeard.ROOT_DIRS:
-            default_index = int(sickbeard.ROOT_DIRS.split('|')[0])
-        else:
-            default_index = 0
+        anime_all_same = True
+        last_anime = None
 
-        if len(root_dirs) > default_index:
-            tmp = root_dirs[default_index]
-            if tmp in root_dirs:
-                root_dirs.remove(tmp)
-                root_dirs = [tmp] + root_dirs
+        sports_all_same = True
+        last_sports = None
 
-        dir_list = []
+        quality_all_same = True
+        last_quality = None
 
-        myDB = db.DBConnection()
-        for root_dir in root_dirs:
-            try:
-                file_list = ek.ek(os.listdir, root_dir)
-            except:
-                continue
+        subtitles_all_same = True
+        last_subtitles = None
 
-            for cur_file in file_list:
+        scene_all_same = True
+        last_scene = None
 
-                cur_path = ek.ek(os.path.normpath, ek.ek(os.path.join, root_dir, cur_file))
-                if not ek.ek(os.path.isdir, cur_path):
-                    continue
+        air_by_date_all_same = True
+        last_air_by_date = None
 
-                cur_dir = {
-                    'dir': cur_path,
-                    'display_dir': '<b>' + ek.ek(os.path.dirname, cur_path) + os.sep + '</b>' + ek.ek(
-                        os.path.basename,
-                        cur_path),
-                }
+        root_dir_list = []
 
-                # see if the folder is in XBMC already
-                dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path])
+        for curShow in showList:
 
-                if dirResults:
-                    cur_dir['added_already'] = True
+            cur_root_dir = ek.ek(os.path.dirname, curShow._location)
+            if cur_root_dir not in root_dir_list:
+                root_dir_list.append(cur_root_dir)
+
+            if archive_firstmatch_all_same:
+                # if we had a value already and this value is different then they're not all the same
+                if last_archive_firstmatch not in (None, curShow.archive_firstmatch):
+                    archive_firstmatch_all_same = False
                 else:
-                    cur_dir['added_already'] = False
+                    last_archive_firstmatch = curShow.archive_firstmatch
 
-                dir_list.append(cur_dir)
+            # if we know they're not all the same then no point even bothering
+            if paused_all_same:
+                # if we had a value already and this value is different then they're not all the same
+                if last_paused not in (None, curShow.paused):
+                    paused_all_same = False
+                else:
+                    last_paused = curShow.paused
 
-                indexer_id = show_name = indexer = None
-                for cur_provider in sickbeard.metadata_provider_dict.values():
-                    if not (indexer_id and show_name):
-                        (indexer_id, show_name, indexer) = cur_provider.retrieveShowMetadata(cur_path)
+            if anime_all_same:
+                # if we had a value already and this value is different then they're not all the same
+                if last_anime not in (None, curShow.is_anime):
+                    anime_all_same = False
+                else:
+                    last_anime = curShow.anime
 
-                        # default to TVDB if indexer was not detected
-                        if show_name and not (indexer or indexer_id):
-                            (sn, idx, id) = helpers.searchIndexerForShowID(show_name, indexer, indexer_id)
+            if flatten_folders_all_same:
+                if last_flatten_folders not in (None, curShow.flatten_folders):
+                    flatten_folders_all_same = False
+                else:
+                    last_flatten_folders = curShow.flatten_folders
 
-                            # set indexer and indexer_id from found info
-                            if not indexer and idx:
-                                indexer = idx
+            if quality_all_same:
+                if last_quality not in (None, curShow.quality):
+                    quality_all_same = False
+                else:
+                    last_quality = curShow.quality
 
-                            if not indexer_id and id:
-                                indexer_id = id
+            if subtitles_all_same:
+                if last_subtitles not in (None, curShow.subtitles):
+                    subtitles_all_same = False
+                else:
+                    last_subtitles = curShow.subtitles
 
-                cur_dir['existing_info'] = (indexer_id, show_name, indexer)
+            if scene_all_same:
+                if last_scene not in (None, curShow.scene):
+                    scene_all_same = False
+                else:
+                    last_scene = curShow.scene
 
-                if indexer_id and helpers.findCertainShow(sickbeard.showList, indexer_id):
-                    cur_dir['added_already'] = True
+            if sports_all_same:
+                if last_sports not in (None, curShow.sports):
+                    sports_all_same = False
+                else:
+                    last_sports = curShow.sports
 
-        t.dirList = dir_list
+            if air_by_date_all_same:
+                if last_air_by_date not in (None, curShow.air_by_date):
+                    air_by_date_all_same = False
+                else:
+                    last_air_by_date = curShow.air_by_date
 
-        return _munge(t)
+        t.showList = toEdit
+        t.archive_firstmatch_value = last_archive_firstmatch if archive_firstmatch_all_same else None
+        t.paused_value = last_paused if paused_all_same else None
+        t.anime_value = last_anime if anime_all_same else None
+        t.flatten_folders_value = last_flatten_folders if flatten_folders_all_same else None
+        t.quality_value = last_quality if quality_all_same else None
+        t.subtitles_value = last_subtitles if subtitles_all_same else None
+        t.scene_value = last_scene if scene_all_same else None
+        t.sports_value = last_sports if sports_all_same else None
+        t.air_by_date_value = last_air_by_date if air_by_date_all_same else None
+        t.root_dir_list = root_dir_list
 
+        return t.respond()
 
-    def newShow(self, show_to_add=None, other_shows=None):
-        """
-        Display the new show page which collects a tvdb id, folder, and extra options and
-        posts them to addNewShow
-        """
-        t = PageTemplate(headers=self.request.headers, file="home_newShow.tmpl")
-        t.submenu = HomeMenu()
 
-        indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add)
+    def massEditSubmit(self, archive_firstmatch=None, paused=None, anime=None, sports=None, scene=None,
+                       flatten_folders=None,
+                       quality_preset=False,
+                       subtitles=None, air_by_date=None, anyQualities=[], bestQualities=[], toEdit=None, *args,
+                       **kwargs):
 
-        if indexer_id and indexer and show_name:
-            use_provided_info = True
-        else:
-            use_provided_info = False
+        dir_map = {}
+        for cur_arg in kwargs:
+            if not cur_arg.startswith('orig_root_dir_'):
+                continue
+            which_index = cur_arg.replace('orig_root_dir_', '')
+            end_dir = kwargs['new_root_dir_' + which_index]
+            dir_map[kwargs[cur_arg]] = end_dir
 
-        # tell the template whether we're giving it show name & Indexer ID
-        t.use_provided_info = use_provided_info
+        showIDs = toEdit.split("|")
+        errors = []
+        for curShow in showIDs:
+            curErrors = []
+            showObj = helpers.findCertainShow(sickbeard.showList, int(curShow))
+            if not showObj:
+                continue
 
-        # use the given show_dir for the indexer search if available
-        if not show_dir:
-            t.default_show_name = ''
-        elif not show_name:
-            t.default_show_name = re.sub(' \(\d{4}\)','', ek.ek(os.path.basename, ek.ek(os.path.normpath, show_dir)).replace('.', ' '))
-        else:
-            t.default_show_name = show_name
+            cur_root_dir = ek.ek(os.path.dirname, showObj._location)
+            cur_show_dir = ek.ek(os.path.basename, showObj._location)
+            if cur_root_dir in dir_map and cur_root_dir != dir_map[cur_root_dir]:
+                new_show_dir = ek.ek(os.path.join, dir_map[cur_root_dir], cur_show_dir)
+                logger.log(
+                    u"For show " + showObj.name + " changing dir from " + showObj._location + " to " + new_show_dir)
+            else:
+                new_show_dir = showObj._location
 
-        # carry a list of other dirs if given
-        if not other_shows:
-            other_shows = []
-        elif type(other_shows) != list:
-            other_shows = [other_shows]
+            if archive_firstmatch == 'keep':
+                new_archive_firstmatch = showObj.archive_firstmatch
+            else:
+                new_archive_firstmatch = True if archive_firstmatch == 'enable' else False
+            new_archive_firstmatch = 'on' if new_archive_firstmatch else 'off'
 
-        if use_provided_info:
-            t.provided_indexer_id = int(indexer_id or 0)
-            t.provided_indexer_name = show_name
+            if paused == 'keep':
+                new_paused = showObj.paused
+            else:
+                new_paused = True if paused == 'enable' else False
+            new_paused = 'on' if new_paused else 'off'
 
-        t.provided_show_dir = show_dir
-        t.other_shows = other_shows
-        t.provided_indexer = int(indexer or sickbeard.INDEXER_DEFAULT)
-        t.indexers = sickbeard.indexerApi().indexers
+            if anime == 'keep':
+                new_anime = showObj.anime
+            else:
+                new_anime = True if anime == 'enable' else False
+            new_anime = 'on' if new_anime else 'off'
 
-        return _munge(t)
+            if sports == 'keep':
+                new_sports = showObj.sports
+            else:
+                new_sports = True if sports == 'enable' else False
+            new_sports = 'on' if new_sports else 'off'
 
-    def recommendedShows(self, *args, **kwargs):
-        """
-        Display the new show page which collects a tvdb id, folder, and extra options and
-        posts them to addNewShow
-        """
-        t = PageTemplate(headers=self.request.headers, file="home_recommendedShows.tmpl")
-        t.submenu = HomeMenu()
+            if scene == 'keep':
+                new_scene = showObj.is_scene
+            else:
+                new_scene = True if scene == 'enable' else False
+            new_scene = 'on' if new_scene else 'off'
 
-        return _munge(t)
+            if air_by_date == 'keep':
+                new_air_by_date = showObj.air_by_date
+            else:
+                new_air_by_date = True if air_by_date == 'enable' else False
+            new_air_by_date = 'on' if new_air_by_date else 'off'
 
-    def getRecommendedShows(self, *args, **kwargs):
-        final_results = []
+            if flatten_folders == 'keep':
+                new_flatten_folders = showObj.flatten_folders
+            else:
+                new_flatten_folders = True if flatten_folders == 'enable' else False
+            new_flatten_folders = 'on' if new_flatten_folders else 'off'
 
-        logger.log(u"Getting recommended shows from Trakt.tv", logger.DEBUG)
-        recommendedlist = TraktCall("recommendations/shows.json/%API%", sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME,
-                                    sickbeard.TRAKT_PASSWORD)
+            if subtitles == 'keep':
+                new_subtitles = showObj.subtitles
+            else:
+                new_subtitles = True if subtitles == 'enable' else False
 
-        if recommendedlist:
-            indexers = ['tvdb_id', 'tvrage_id']
-            map(final_results.append, (
-                [int(show[indexers[sickbeard.TRAKT_DEFAULT_INDEXER - 1]]), show['url'], show['title'], show['overview'],
-                 datetime.date.fromtimestamp(int(show['first_aired']) / 1000.0).strftime('%Y%m%d')]
-                for show in recommendedlist if not helpers.findCertainShow(sickbeard.showList, [
-                int(show[indexers[sickbeard.TRAKT_DEFAULT_INDEXER - 1]])])))
+            new_subtitles = 'on' if new_subtitles else 'off'
 
-        return json.dumps({'results': final_results})
+            if quality_preset == 'keep':
+                anyQualities, bestQualities = Quality.splitQuality(showObj.quality)
 
-    def addRecommendedShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
-                           anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
-                           fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None,
-                           scene=None):
+            exceptions_list = []
 
-        indexer = 1
-        indexer_name = sickbeard.indexerApi(int(indexer)).name
-        show_url = whichSeries.split('|')[1]
-        indexer_id = whichSeries.split('|')[0]
-        show_name = whichSeries.split('|')[2]
+            curErrors += self.editShow(curShow, new_show_dir, anyQualities,
+                                       bestQualities, exceptions_list,
+                                       archive_firstmatch=new_archive_firstmatch,
+                                       flatten_folders=new_flatten_folders,
+                                       paused=new_paused, sports=new_sports,
+                                       subtitles=new_subtitles, anime=new_anime,
+                                       scene=new_scene, air_by_date=new_air_by_date,
+                                       directCall=True)
 
-        return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]),
-                               indexerLang, rootDir,
-                               defaultStatus,
-                               anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows,
-                               skipShow, providedIndexer, anime, scene)
+            if curErrors:
+                logger.log(u"Errors: " + str(curErrors), logger.ERROR)
+                errors.append('<b>%s:</b>\n<ul>' % showObj.name + ' '.join(
+                    ['<li>%s</li>' % error for error in curErrors]) + "</ul>")
 
-    def trendingShows(self, *args, **kwargs):
-        """
-        Display the new show page which collects a tvdb id, folder, and extra options and
-        posts them to addNewShow
-        """
-        t = PageTemplate(headers=self.request.headers, file="home_trendingShows.tmpl")
-        t.submenu = HomeMenu()
+        if len(errors) > 0:
+            ui.notifications.error('%d error%s while saving changes:' % (len(errors), "" if len(errors) == 1 else "s"),
+                                   " ".join(errors))
 
-        t.trending_shows = []
+        return self.redirect("/manage/")
 
-        trending_shows = TraktCall("shows/trending.json/%API%", sickbeard.TRAKT_API_KEY)
-        if trending_shows:
-            for show in trending_shows:
-                try:
-                    if not helpers.findCertainShow(sickbeard.showList, [int(show['tvdb_id']), int(show['tvrage_id'])]):
-                        t.trending_shows += [show]
-                except exceptions.MultipleShowObjectsException:
-                    continue
 
-        return _munge(t)
+    def massUpdate(self, toUpdate=None, toRefresh=None, toRename=None, toDelete=None, toRemove=None, toMetadata=None,
+                   toSubtitle=None):
 
-    def existingShows(self, *args, **kwargs):
-        """
-        Prints out the page to add existing shows from a root dir
-        """
-        t = PageTemplate(headers=self.request.headers, file="home_addExistingShow.tmpl")
-        t.submenu = HomeMenu()
+        if toUpdate is not None:
+            toUpdate = toUpdate.split('|')
+        else:
+            toUpdate = []
 
-        return _munge(t)
+        if toRefresh is not None:
+            toRefresh = toRefresh.split('|')
+        else:
+            toRefresh = []
 
-    def addTraktShow(self, indexer_id, showName):
-        if helpers.findCertainShow(sickbeard.showList, int(indexer_id)):
-            return
+        if toRename is not None:
+            toRename = toRename.split('|')
+        else:
+            toRename = []
 
-        if sickbeard.ROOT_DIRS:
-            root_dirs = sickbeard.ROOT_DIRS.split('|')
-            location = root_dirs[int(root_dirs[0]) + 1]
+        if toSubtitle is not None:
+            toSubtitle = toSubtitle.split('|')
         else:
-            location = None
+            toSubtitle = []
 
-        if location:
-            show_dir = ek.ek(os.path.join, location, helpers.sanitizeFileName(showName))
-            dir_exists = helpers.makeDir(show_dir)
-            if not dir_exists:
-                logger.log(u"Unable to create the folder " + show_dir + ", can't add the show", logger.ERROR)
-                return
-            else:
-                helpers.chmodAsParent(show_dir)
+        if toDelete is not None:
+            toDelete = toDelete.split('|')
+        else:
+            toDelete = []
 
-            sickbeard.showQueueScheduler.action.addShow(1, int(indexer_id), show_dir,
-                                                        default_status=sickbeard.STATUS_DEFAULT,
-                                                        quality=sickbeard.QUALITY_DEFAULT,
-                                                        flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT,
-                                                        subtitles=sickbeard.SUBTITLES_DEFAULT,
-                                                        anime=sickbeard.ANIME_DEFAULT,
-                                                        scene=sickbeard.SCENE_DEFAULT)
+        if toRemove is not None:
+            toRemove = toRemove.split('|')
+        else:
+            toRemove = []
 
-            ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
+        if toMetadata is not None:
+            toMetadata = toMetadata.split('|')
         else:
-            logger.log(u"There was an error creating the show, no root directory setting found", logger.ERROR)
-            return "No root directories setup, please go back and add one."
+            toMetadata = []
 
-        # done adding show
-        redirect('/home/')
+        errors = []
+        refreshes = []
+        updates = []
+        renames = []
+        subtitles = []
 
-    def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
-                   anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
-                   fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None,
-                   scene=None):
-        """
-        Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are
-        provided then it forwards back to newShow, if not it goes to /home.
-        """
+        for curShowID in set(toUpdate + toRefresh + toRename + toSubtitle + toDelete + toRemove + toMetadata):
 
-        # grab our list of other dirs if given
-        if not other_shows:
-            other_shows = []
-        elif type(other_shows) != list:
-            other_shows = [other_shows]
+            if curShowID == '':
+                continue
 
-        def finishAddShow():
-            # if there are no extra shows then go home
-            if not other_shows:
-                redirect('/home/')
+            showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(curShowID))
 
-            # peel off the next one
-            next_show_dir = other_shows[0]
-            rest_of_show_dirs = other_shows[1:]
+            if showObj is None:
+                continue
 
-            # go to add the next show
-            return self.newShow(next_show_dir, rest_of_show_dirs)
+            if curShowID in toDelete:
+                showObj.deleteShow(True)
+                # don't do anything else if it's being deleted
+                continue
 
-        # if we're skipping then behave accordingly
-        if skipShow:
-            return finishAddShow()
+            if curShowID in toRemove:
+                showObj.deleteShow()
+                # don't do anything else if it's being remove
+                continue
 
-        # sanity check on our inputs
-        if (not rootDir and not fullShowPath) or not whichSeries:
-            return "Missing params, no Indexer ID or folder:" + repr(whichSeries) + " and " + repr(
-                rootDir) + "/" + repr(fullShowPath)
+            if curShowID in toUpdate:
+                try:
+                    sickbeard.showQueueScheduler.action.updateShow(showObj, True)
+                    updates.append(showObj.name)
+                except exceptions.CantUpdateException, e:
+                    errors.append("Unable to update show " + showObj.name + ": " + ex(e))
 
-        # figure out what show we're adding and where
-        series_pieces = whichSeries.split('|')
-        if (whichSeries and rootDir) or (whichSeries and fullShowPath and len(series_pieces) > 1):
-            if len(series_pieces) < 6:
-                logger.log("Unable to add show due to show selection. Not anough arguments: %s" % (repr(series_pieces)),
-                           logger.ERROR)
-                ui.notifications.error("Unknown error. Unable to add show due to problem with show selection.")
-                redirect('/home/addShows/existingShows/')
-            indexer = int(series_pieces[1])
-            indexer_id = int(series_pieces[3])
-            show_name = series_pieces[4]
-        else:
-            # if no indexer was provided use the default indexer set in General settings
-            if not providedIndexer:
-                providedIndexer = sickbeard.INDEXER_DEFAULT
+            # don't bother refreshing shows that were updated anyway
+            if curShowID in toRefresh and curShowID not in toUpdate:
+                try:
+                    sickbeard.showQueueScheduler.action.refreshShow(showObj)
+                    refreshes.append(showObj.name)
+                except exceptions.CantRefreshException, e:
+                    errors.append("Unable to refresh show " + showObj.name + ": " + ex(e))
 
-            indexer = int(providedIndexer)
-            indexer_id = int(whichSeries)
-            show_name = os.path.basename(os.path.normpath(fullShowPath))
+            if curShowID in toRename:
+                sickbeard.showQueueScheduler.action.renameShowEpisodes(showObj)
+                renames.append(showObj.name)
 
-        # use the whole path if it's given, or else append the show name to the root dir to get the full show path
-        if fullShowPath:
-            show_dir = ek.ek(os.path.normpath, fullShowPath)
-        else:
-            show_dir = ek.ek(os.path.join, rootDir, helpers.sanitizeFileName(show_name))
+            if curShowID in toSubtitle:
+                sickbeard.showQueueScheduler.action.downloadSubtitles(showObj)
+                subtitles.append(showObj.name)
 
-        # 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.notifications.error("Unable to add show", "Folder " + show_dir + " exists already")
-            redirect('/home/addShows/existingShows/')
+        if len(errors) > 0:
+            ui.notifications.error("Errors encountered",
+                                   '<br >\n'.join(errors))
 
-        # don't create show dir if config says not to
-        if sickbeard.ADD_SHOWS_WO_DIR:
-            logger.log(u"Skipping initial creation of " + show_dir + " due to config.ini setting")
-        else:
-            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.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)
+        messageDetail = ""
 
-        # prepare the inputs for passing along
-        scene = config.checkbox_to_value(scene)
-        anime = config.checkbox_to_value(anime)
-        flatten_folders = config.checkbox_to_value(flatten_folders)
-        subtitles = config.checkbox_to_value(subtitles)
+        if len(updates) > 0:
+            messageDetail += "<br /><b>Updates</b><br /><ul><li>"
+            messageDetail += "</li><li>".join(updates)
+            messageDetail += "</li></ul>"
 
-        if not anyQualities:
-            anyQualities = []
-        if not bestQualities:
-            bestQualities = []
-        if type(anyQualities) != list:
-            anyQualities = [anyQualities]
-        if type(bestQualities) != list:
-            bestQualities = [bestQualities]
-        newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
+        if len(refreshes) > 0:
+            messageDetail += "<br /><b>Refreshes</b><br /><ul><li>"
+            messageDetail += "</li><li>".join(refreshes)
+            messageDetail += "</li></ul>"
 
-        # add the show
-        sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality,
-                                                    flatten_folders, indexerLang, subtitles, anime,
-                                                    scene)  # @UndefinedVariable
-        ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
+        if len(renames) > 0:
+            messageDetail += "<br /><b>Renames</b><br /><ul><li>"
+            messageDetail += "</li><li>".join(renames)
+            messageDetail += "</li></ul>"
 
-        return finishAddShow()
+        if len(subtitles) > 0:
+            messageDetail += "<br /><b>Subtitles</b><br /><ul><li>"
+            messageDetail += "</li><li>".join(subtitles)
+            messageDetail += "</li></ul>"
 
-    def split_extra_show(self, extra_show):
-        if not extra_show:
-            return (None, None, None, None)
-        split_vals = extra_show.split('|')
-        if len(split_vals) < 4:
-            indexer = split_vals[0]
-            show_dir = split_vals[1]
-            return (indexer, show_dir, None, None)
-        indexer = split_vals[0]
-        show_dir = split_vals[1]
-        indexer_id = split_vals[2]
-        show_name = '|'.join(split_vals[3:])
+        if len(updates + refreshes + renames + subtitles) > 0:
+            ui.notifications.message("The following actions were queued:",
+                                     messageDetail)
 
-        return (indexer, show_dir, indexer_id, show_name)
+        return self.redirect("/manage/")
 
 
-    def addExistingShows(self, shows_to_add=None, promptForSettings=None):
-        """
-        Receives a dir list and add them. Adds the ones with given TVDB IDs first, then forwards
-        along to the newShow page.
-        """
+    def manageTorrents(self):
 
-        # grab a list of other shows to add, if provided
-        if not shows_to_add:
-            shows_to_add = []
-        elif type(shows_to_add) != list:
-            shows_to_add = [shows_to_add]
+        t = PageTemplate(rh=self, file="manage_torrents.tmpl")
+        t.info_download_station = ''
+        t.submenu = self.ManageMenu()
 
-        shows_to_add = [urllib.unquote_plus(x) for x in shows_to_add]
+        if re.search('localhost', sickbeard.TORRENT_HOST):
 
-        promptForSettings = config.checkbox_to_value(promptForSettings)
+            if sickbeard.LOCALHOST_IP == '':
+                t.webui_url = re.sub('localhost', helpers.get_lan_ip(), sickbeard.TORRENT_HOST)
+            else:
+                t.webui_url = re.sub('localhost', sickbeard.LOCALHOST_IP, sickbeard.TORRENT_HOST)
+        else:
+            t.webui_url = sickbeard.TORRENT_HOST
 
-        indexer_id_given = []
-        dirs_only = []
-        # separate all the ones with Indexer IDs
-        for cur_dir in shows_to_add:
-            if '|' in cur_dir:
-                split_vals = cur_dir.split('|')
-                if len(split_vals) < 3:
-                    dirs_only.append(cur_dir)
-            if not '|' in cur_dir:
-                dirs_only.append(cur_dir)
+        if sickbeard.TORRENT_METHOD == 'utorrent':
+            t.webui_url = '/'.join(s.strip('/') for s in (t.webui_url, 'gui/'))
+        if sickbeard.TORRENT_METHOD == 'download_station':
+            if helpers.check_url(t.webui_url + 'download/'):
+                t.webui_url = t.webui_url + 'download/'
             else:
-                indexer, show_dir, indexer_id, show_name = self.split_extra_show(cur_dir)
+                t.info_download_station = '<p>To have a better experience please set the Download Station alias as <code>download</code>, you can check this setting in the Synology DSM <b>Control Panel</b> > <b>Application Portal</b>. Make sure you allow DSM to be embedded with iFrames too in <b>Control Panel</b> > <b>DSM Settings</b> > <b>Security</b>.</p><br/><p>There is more information about this available <a href="https://github.com/midgetspy/Sick-Beard/pull/338">here</a>.</p><br/>'
 
-                if not show_dir or not indexer_id or not show_name:
-                    continue
+        return t.respond()
 
-                indexer_id_given.append((int(indexer), show_dir, int(indexer_id), show_name))
 
+    def failedDownloads(self, limit=100, toRemove=None):
 
-        # if they want me to prompt for settings then I will just carry on to the newShow page
-        if promptForSettings and shows_to_add:
-            return self.newShow(shows_to_add[0], shows_to_add[1:])
+        myDB = db.DBConnection('failed.db')
 
-        # if they don't want me to prompt for settings then I can just add all the nfo shows now
-        num_added = 0
-        for cur_show in indexer_id_given:
-            indexer, show_dir, indexer_id, show_name = cur_show
+        if limit == "0":
+            sqlResults = myDB.select("SELECT * FROM failed")
+        else:
+            sqlResults = myDB.select("SELECT * FROM failed LIMIT ?", [limit])
 
-            if indexer is not None and indexer_id is not None:
-                # add the show
-                sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir,
-                                                            default_status=sickbeard.STATUS_DEFAULT,
-                                                            quality=sickbeard.QUALITY_DEFAULT,
-                                                            flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT,
-                                                            subtitles=sickbeard.SUBTITLES_DEFAULT,
-                                                            anime=sickbeard.ANIME_DEFAULT,
-                                                            scene=sickbeard.SCENE_DEFAULT)
-                num_added += 1
+        toRemove = toRemove.split("|") if toRemove is not None else []
 
-        if num_added:
-            ui.notifications.message("Shows Added",
-                                     "Automatically added " + str(num_added) + " from their existing metadata files")
+        for release in toRemove:
+            myDB.action("DELETE FROM failed WHERE failed.release = ?", [release])
 
-        # if we're done then go home
-        if not dirs_only:
-            redirect('/home/')
+        if toRemove:
+            return self.redirect('/manage/failedDownloads/')
 
-        # for the remaining shows we need to prompt for each one, so forward this on to the newShow page
-        return self.newShow(dirs_only[0], dirs_only[1:])
+        t = PageTemplate(rh=self, file="manage_failedDownloads.tmpl")
+        t.failedResults = sqlResults
+        t.limit = limit
+        t.submenu = self.ManageMenu()
 
+        return t.respond()
 
-ErrorLogsMenu = [
-    {'title': 'Clear Errors', 'path': 'errorlogs/clearerrors/'},
-    # { 'title': 'View Log',  'path': 'errorlogs/viewlog'  },
-]
 
+@route('/manage/manageSearches(/?.*)')
+class ManageSearches(Manage):
+    def __init__(self, *args, **kwargs):
+        super(ManageSearches, self).__init__(*args, **kwargs)
 
-class ErrorLogs(MainHandler):
-    def index(self, *args, **kwargs):
+    def index(self):
+        t = PageTemplate(rh=self, 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.dailySearchStatus = sickbeard.dailySearchScheduler.action.amActive
+        t.findPropersStatus = sickbeard.properFinderScheduler.action.amActive
+        t.queueLength = sickbeard.searchQueueScheduler.action.queue_length()
 
-        t = PageTemplate(headers=self.request.headers, file="errorlogs.tmpl")
-        t.submenu = ErrorLogsMenu
+        t.submenu = self.ManageMenu()
 
-        return _munge(t)
+        return t.respond()
 
+    def forceBacklog(self):
+        # force it to run the next time it looks
+        result = sickbeard.backlogSearchScheduler.forceRun()
+        if result:
+            logger.log(u"Backlog search forced")
+            ui.notifications.message('Backlog search started')
 
-    def clearerrors(self, *args, **kwargs):
-        classes.ErrorViewer.clear()
-        redirect("/errorlogs/")
+        return self.redirect("/manage/manageSearches/")
 
+    def forceSearch(self):
 
-    def viewlog(self, minLevel=logger.MESSAGE, maxLines=500):
+        # force it to run the next time it looks
+        result = sickbeard.dailySearchScheduler.forceRun()
+        if result:
+            logger.log(u"Daily search forced")
+            ui.notifications.message('Daily search started')
 
-        t = PageTemplate(headers=self.request.headers, file="viewlogs.tmpl")
-        t.submenu = ErrorLogsMenu
+        return self.redirect("/manage/manageSearches/")
 
-        minLevel = int(minLevel)
 
-        data = []
-        if os.path.isfile(logger.sb_log_instance.log_file_path):
-            with ek.ek(open, logger.sb_log_instance.log_file_path) as f:
-                data = f.readlines()
+    def forceFindPropers(self):
 
-        regex = "^(\d\d\d\d)\-(\d\d)\-(\d\d)\s*(\d\d)\:(\d\d):(\d\d)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$"
+        # force it to run the next time it looks
+        result = sickbeard.properFinderScheduler.forceRun()
+        if result:
+            logger.log(u"Find propers search forced")
+            ui.notifications.message('Find propers search started')
 
-        finalData = []
+        return self.redirect("/manage/manageSearches/")
 
-        numLines = 0
-        lastLine = False
-        numToShow = min(maxLines, len(data))
 
-        for x in reversed(data):
+    def pauseBacklog(self, paused=None):
+        if paused == "1":
+            sickbeard.searchQueueScheduler.action.pause_backlog()
+        else:
+            sickbeard.searchQueueScheduler.action.unpause_backlog()
 
-            x = ek.toUnicode(x)
-            match = re.match(regex, x)
+        return self.redirect("/manage/manageSearches/")
 
-            if match:
-                level = match.group(7)
-                if level not in logger.reverseNames:
-                    lastLine = False
-                    continue
 
-                if logger.reverseNames[level] >= minLevel:
-                    lastLine = True
-                    finalData.append(x)
-                else:
-                    lastLine = False
-                    continue
+@route('/history(/?.*)')
+class History(WebRoot):
+    def __init__(self, *args, **kwargs):
+        super(History, self).__init__(*args, **kwargs)
 
-            elif lastLine:
-                finalData.append("AA" + x)
+    def index(self, limit=100):
+
+        # sqlResults = myDB.select("SELECT h.*, show_name, name FROM history h, tv_shows s, tv_episodes e WHERE h.showid=s.indexer_id AND h.showid=e.showid AND h.season=e.season AND h.episode=e.episode ORDER BY date DESC LIMIT "+str(numPerPage*(p-1))+", "+str(numPerPage))
+        myDB = db.DBConnection()
+        if limit == "0":
+            sqlResults = myDB.select(
+                "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC")
+        else:
+            sqlResults = myDB.select(
+                "SELECT h.*, show_name FROM history h, tv_shows s WHERE h.showid=s.indexer_id ORDER BY date DESC LIMIT ?",
+                [limit])
+
+        history = {'show_id': 0, 'season': 0, 'episode': 0, 'quality': 0,
+                   'actions': [{'time': '', 'action': '', 'provider': ''}]}
+        compact = []
 
-            numLines += 1
+        for sql_result in sqlResults:
 
-            if numLines >= numToShow:
-                break
+            if not any((history['show_id'] == sql_result['showid']
+                        and history['season'] == sql_result['season']
+                        and history['episode'] == sql_result['episode']
+                        and history['quality'] == sql_result['quality'])
+                       for history in compact):
 
-        result = "".join(finalData)
+                history = {}
+                history['show_id'] = sql_result['showid']
+                history['season'] = sql_result['season']
+                history['episode'] = sql_result['episode']
+                history['quality'] = sql_result['quality']
+                history['show_name'] = sql_result['show_name']
+                history['resource'] = sql_result['resource']
 
-        t.logLines = result
-        t.minLevel = minLevel
+                action = {}
+                history['actions'] = []
 
-        return _munge(t)
+                action['time'] = sql_result['date']
+                action['action'] = sql_result['action']
+                action['provider'] = sql_result['provider']
+                action['resource'] = sql_result['resource']
+                history['actions'].append(action)
+                history['actions'].sort(key=lambda x: x['time'])
+                compact.append(history)
+            else:
+                index = [i for i, dict in enumerate(compact) \
+                         if dict['show_id'] == sql_result['showid'] \
+                         and dict['season'] == sql_result['season'] \
+                         and dict['episode'] == sql_result['episode']
+                         and dict['quality'] == sql_result['quality']][0]
 
+                action = {}
+                history = compact[index]
 
-class Home(MainHandler):
-    def is_alive(self, *args, **kwargs):
-        if 'callback' in kwargs and '_' in kwargs:
-            callback, _ = kwargs['callback'], kwargs['_']
-        else:
-            return "Error: Unsupported Request. Send jsonp request with 'callback' variable in the query string."
+                action['time'] = sql_result['date']
+                action['action'] = sql_result['action']
+                action['provider'] = sql_result['provider']
+                action['resource'] = sql_result['resource']
+                history['actions'].append(action)
+                history['actions'].sort(key=lambda x: x['time'], reverse=True)
 
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
-        self.set_header('Content-Type', 'text/javascript')
-        self.set_header('Access-Control-Allow-Origin', '*')
-        self.set_header('Access-Control-Allow-Headers', 'x-requested-with')
+        t = PageTemplate(rh=self, file="history.tmpl")
+        t.historyResults = sqlResults
+        t.compactResults = compact
+        t.limit = limit
+        t.submenu = [
+            {'title': 'Clear History', 'path': 'history/clearHistory'},
+            {'title': 'Trim History', 'path': 'history/trimHistory'},
+        ]
 
-        if sickbeard.started:
-            return callback + '(' + json.dumps(
-                {"msg": str(sickbeard.PID)}) + ');'
-        else:
-            return callback + '(' + json.dumps({"msg": "nope"}) + ');'
+        return t.respond()
 
 
-    def index(self, *args, **kwargs):
+    def clearHistory(self):
 
-        t = PageTemplate(headers=self.request.headers, file="home.tmpl")
-        if sickbeard.ANIME_SPLIT_HOME:
-            shows = []
-            anime = []
-            for show in sickbeard.showList:
-                if show.is_anime:
-                    anime.append(show)
-                else:
-                    shows.append(show)
-            t.showlists = [["Shows", shows],
-                           ["Anime", anime]]
-        else:
-            t.showlists = [["Shows", sickbeard.showList]]
+        myDB = db.DBConnection()
+        myDB.action("DELETE FROM history WHERE 1=1")
 
-        t.submenu = HomeMenu()
+        ui.notifications.message('History cleared')
+        return self.redirect("/history/")
 
-        return _munge(t)
 
-    addShows = NewHomeAddShows
-    postprocess = HomePostProcess
+    def trimHistory(self):
 
-    def testSABnzbd(self, host=None, username=None, password=None, apikey=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        myDB = db.DBConnection()
+        myDB.action("DELETE FROM history WHERE date < " + str(
+            (datetime.datetime.today() - datetime.timedelta(days=30)).strftime(history.dateFormat)))
 
-        host = config.clean_url(host)
+        ui.notifications.message('Removed history entries greater than 30 days old')
+        return self.redirect("/history/")
+
+
+@route('/config(/?.*)')
+class Config(WebRoot):
+    def __init__(self, *args, **kwargs):
+        super(Config, self).__init__(*args, **kwargs)
+
+    def ConfigMenu(self):
+        menu = [
+            {'title': 'General', 'path': 'config/general/'},
+            {'title': 'Backup/Restore', 'path': 'config/backuprestore/'},
+            {'title': 'Search Settings', 'path': 'config/search/'},
+            {'title': 'Search Providers', 'path': 'config/providers/'},
+            {'title': 'Subtitles Settings', 'path': 'config/subtitles/'},
+            {'title': 'Post Processing', 'path': 'config/postProcessing/'},
+            {'title': 'Notifications', 'path': 'config/notifications/'},
+            {'title': 'Anime', 'path': 'config/anime/'},
+        ]
 
-        connection, accesMsg = sab.getSabAccesMethod(host, username, password, apikey)
-        if connection:
-            authed, authMsg = sab.testAuthentication(host, username, password, apikey)  # @UnusedVariable
-            if authed:
-                return "Success. Connected and authenticated"
-            else:
-                return "Authentication failed. SABnzbd expects '" + accesMsg + "' as authentication method"
-        else:
-            return "Unable to connect to host"
+        return menu
 
+    def index(self):
+        t = PageTemplate(rh=self, file="config.tmpl")
+        t.submenu = self.ConfigMenu()
 
-    def testTorrent(self, torrent_method=None, host=None, username=None, password=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        return t.respond()
 
-        host = config.clean_url(host)
 
-        client = clients.getClientIstance(torrent_method)
+@route('/config/general(/?.*)')
+class ConfigGeneral(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigGeneral, self).__init__(*args, **kwargs)
 
-        connection, accesMsg = client(host, username, password).testAuthentication()
+    def index(self):
+        t = PageTemplate(rh=self, file="config_general.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
 
-        return accesMsg
 
+    def generateApiKey(self):
+        return helpers.generateApiKey()
 
-    def testGrowl(self, host=None, password=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+    def saveRootDirs(self, rootDirString=None):
+        sickbeard.ROOT_DIRS = rootDirString
 
-        host = config.clean_host(host, default_port=23053)
+    def saveAddShowDefaults(self, defaultStatus, anyQualities, bestQualities, defaultFlattenFolders, subtitles=False,
+                            anime=False, scene=False):
 
-        result = notifiers.growl_notifier.test_notify(host, password)
-        if password is None or password == '':
-            pw_append = ''
+        if anyQualities:
+            anyQualities = anyQualities.split(',')
         else:
-            pw_append = " with password: " + password
+            anyQualities = []
 
-        if result:
-            return "Registered and Tested growl successfully " + urllib.unquote_plus(host) + pw_append
+        if bestQualities:
+            bestQualities = bestQualities.split(',')
         else:
-            return "Registration and Testing of growl failed " + urllib.unquote_plus(host) + pw_append
+            bestQualities = []
 
+        newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
 
-    def testProwl(self, prowl_api=None, prowl_priority=0):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.STATUS_DEFAULT = int(defaultStatus)
+        sickbeard.QUALITY_DEFAULT = int(newQuality)
 
-        result = notifiers.prowl_notifier.test_notify(prowl_api, prowl_priority)
-        if result:
-            return "Test prowl notice sent successfully"
-        else:
-            return "Test prowl notice failed"
+        sickbeard.FLATTEN_FOLDERS_DEFAULT = config.checkbox_to_value(defaultFlattenFolders)
+        sickbeard.SUBTITLES_DEFAULT = config.checkbox_to_value(subtitles)
 
+        sickbeard.ANIME_DEFAULT = config.checkbox_to_value(anime)
+        sickbeard.SCENE_DEFAULT = config.checkbox_to_value(scene)
 
-    def testBoxcar(self, username=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.save_config()
 
-        result = notifiers.boxcar_notifier.test_notify(username)
-        if result:
-            return "Boxcar notification succeeded. Check your Boxcar clients to make sure it worked"
-        else:
-            return "Error sending Boxcar notification"
+    def saveGeneral(self, log_dir=None, web_port=None, web_log=None, encryption_version=None, web_ipv6=None,
+                    update_shows_on_start=None, trash_remove_show=None, trash_rotate_logs=None, update_frequency=None,
+                    launch_browser=None, web_username=None,
+                    use_api=None, api_key=None, indexer_default=None, timezone_display=None, cpu_preset=None,
+                    web_password=None, version_notify=None, enable_https=None, https_cert=None, https_key=None,
+                    handle_reverse_proxy=None, sort_article=None, auto_update=None, notify_on_update=None,
+                    proxy_setting=None, proxy_indexers=None, anon_redirect=None, git_path=None, git_remote=None,
+                    calendar_unprotected=None,
+                    fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None,
+                    indexer_timeout=None, play_videos=None, rootDir=None, theme_name=None):
 
+        results = []
 
-    def testBoxcar2(self, accesstoken=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        # Misc
+        sickbeard.PLAY_VIDEOS = config.checkbox_to_value(play_videos)
+        sickbeard.LAUNCH_BROWSER = config.checkbox_to_value(launch_browser)
+        config.change_VERSION_NOTIFY(config.checkbox_to_value(version_notify))
+        sickbeard.AUTO_UPDATE = config.checkbox_to_value(auto_update)
+        sickbeard.NOTIFY_ON_UPDATE = config.checkbox_to_value(notify_on_update)
+        # sickbeard.LOG_DIR is set in config.change_LOG_DIR()
 
-        result = notifiers.boxcar2_notifier.test_notify(accesstoken)
-        if result:
-            return "Boxcar2 notification succeeded. Check your Boxcar2 clients to make sure it worked"
-        else:
-            return "Error sending Boxcar2 notification"
+        sickbeard.UPDATE_SHOWS_ON_START = config.checkbox_to_value(update_shows_on_start)
+        sickbeard.TRASH_REMOVE_SHOW = config.checkbox_to_value(trash_remove_show)
+        sickbeard.TRASH_ROTATE_LOGS = config.checkbox_to_value(trash_rotate_logs)
+        config.change_UPDATE_FREQUENCY(update_frequency)
+        sickbeard.LAUNCH_BROWSER = config.checkbox_to_value(launch_browser)
+        sickbeard.SORT_ARTICLE = config.checkbox_to_value(sort_article)
+        sickbeard.CPU_PRESET = cpu_preset
+        sickbeard.ANON_REDIRECT = anon_redirect
+        sickbeard.PROXY_SETTING = proxy_setting
+        sickbeard.PROXY_INDEXERS = config.checkbox_to_value(proxy_indexers)
+        sickbeard.GIT_PATH = git_path
+        sickbeard.GIT_REMOTE = git_remote
+        sickbeard.CALENDAR_UNPROTECTED = config.checkbox_to_value(calendar_unprotected)
+        # sickbeard.LOG_DIR is set in config.change_LOG_DIR()
 
+        sickbeard.WEB_PORT = config.to_int(web_port)
+        sickbeard.WEB_IPV6 = config.checkbox_to_value(web_ipv6)
+        # sickbeard.WEB_LOG is set in config.change_LOG_DIR()
+        sickbeard.ENCRYPTION_VERSION = config.checkbox_to_value(encryption_version)
+        sickbeard.WEB_USERNAME = web_username
+        sickbeard.WEB_PASSWORD = web_password
 
-    def testPushover(self, userKey=None, apiKey=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.FUZZY_DATING = config.checkbox_to_value(fuzzy_dating)
+        sickbeard.TRIM_ZERO = config.checkbox_to_value(trim_zero)
 
-        result = notifiers.pushover_notifier.test_notify(userKey, apiKey)
-        if result:
-            return "Pushover notification succeeded. Check your Pushover clients to make sure it worked"
-        else:
-            return "Error sending Pushover notification"
+        if date_preset:
+            sickbeard.DATE_PRESET = date_preset
+            discarded_na_data = date_preset_na
 
+        if indexer_default:
+            sickbeard.INDEXER_DEFAULT = config.to_int(indexer_default)
 
-    def twitterStep1(self, *args, **kwargs):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        if indexer_timeout:
+            sickbeard.INDEXER_TIMEOUT = config.to_int(indexer_timeout)
 
-        return notifiers.twitter_notifier._get_authorization()
+        if time_preset:
+            sickbeard.TIME_PRESET_W_SECONDS = time_preset
+            sickbeard.TIME_PRESET = sickbeard.TIME_PRESET_W_SECONDS.replace(u":%S", u"")
 
+        sickbeard.TIMEZONE_DISPLAY = timezone_display
 
-    def twitterStep2(self, key):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        if not config.change_LOG_DIR(log_dir, web_log):
+            results += ["Unable to create directory " + os.path.normpath(log_dir) + ", log directory not changed."]
 
-        result = notifiers.twitter_notifier._get_credentials(key)
-        logger.log(u"result: " + str(result))
-        if result:
-            return "Key verification successful"
-        else:
-            return "Unable to verify key"
+        sickbeard.USE_API = config.checkbox_to_value(use_api)
+        sickbeard.API_KEY = api_key
 
+        sickbeard.ENABLE_HTTPS = config.checkbox_to_value(enable_https)
 
-    def testTwitter(self, *args, **kwargs):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        if not config.change_HTTPS_CERT(https_cert):
+            results += [
+                "Unable to create directory " + os.path.normpath(https_cert) + ", https cert directory not changed."]
 
-        result = notifiers.twitter_notifier.test_notify()
-        if result:
-            return "Tweet successful, check your twitter to make sure it worked"
-        else:
-            return "Error sending tweet"
+        if not config.change_HTTPS_KEY(https_key):
+            results += [
+                "Unable to create directory " + os.path.normpath(https_key) + ", https key directory not changed."]
 
+        sickbeard.HANDLE_REVERSE_PROXY = config.checkbox_to_value(handle_reverse_proxy)
 
-    def testXBMC(self, host=None, username=None, password=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.THEME_NAME = theme_name
 
-        host = config.clean_hosts(host)
-        finalResult = ''
-        for curHost in [x.strip() for x in host.split(",")]:
-            curResult = notifiers.xbmc_notifier.test_notify(urllib.unquote_plus(curHost), username, password)
-            if len(curResult.split(":")) > 2 and 'OK' in curResult.split(":")[2]:
-                finalResult += "Test XBMC notice sent successfully to " + urllib.unquote_plus(curHost)
-            else:
-                finalResult += "Test XBMC notice failed to " + urllib.unquote_plus(curHost)
-            finalResult += "<br />\n"
+        sickbeard.save_config()
 
-        return finalResult
+        if len(results) > 0:
+            for x in results:
+                logger.log(x, logger.ERROR)
+            ui.notifications.error('Error(s) Saving Configuration',
+                                   '<br />\n'.join(results))
+        else:
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
+        return self.redirect("/config/general/")
 
-    def testPLEX(self, host=None, username=None, password=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        finalResult = ''
-        for curHost in [x.strip() for x in host.split(",")]:
-            curResult = notifiers.plex_notifier.test_notify(urllib.unquote_plus(curHost), username, password)
-            if len(curResult.split(":")) > 2 and 'OK' in curResult.split(":")[2]:
-                finalResult += "Test Plex notice sent successfully to " + urllib.unquote_plus(curHost)
-            else:
-                finalResult += "Test Plex notice failed to " + urllib.unquote_plus(curHost)
-            finalResult += "<br />\n"
+@route('/config/backuprestore(/?.*)')
+class ConfigBackupRestore(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigBackupRestore, self).__init__(*args, **kwargs)
 
-        return finalResult
+    def index(self):
+        t = PageTemplate(rh=self, file="config_backuprestore.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
+
+    def backup(self, backupDir=None):
 
+        finalResult = ''
 
-    def testLibnotify(self, *args, **kwargs):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        if backupDir:
+            source = [os.path.join(sickbeard.DATA_DIR, 'sickbeard.db'), sickbeard.CONFIG_FILE]
+            target = os.path.join(backupDir, 'sickrage-' + time.strftime('%Y%m%d%H%M%S') + '.zip')
 
-        if notifiers.libnotify_notifier.test_notify():
-            return "Tried sending desktop notification via libnotify"
+            if helpers.makeZip(source, target):
+                finalResult += "Successful backup to " + target
+            else:
+                finalResult += "Backup FAILED"
         else:
-            return notifiers.libnotify.diagnose()
+            finalResult += "You need to choose a folder to save your backup to!"
 
+        finalResult += "<br />\n"
 
-    def testNMJ(self, host=None, database=None, mount=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        return finalResult
 
-        host = config.clean_host(host)
-        result = notifiers.nmj_notifier.test_notify(urllib.unquote_plus(host), database, mount)
-        if result:
-            return "Successfully started the scan update"
-        else:
-            return "Test failed to start the scan update"
 
+    def restore(self, backupFile=None):
 
-    def settingsNMJ(self, host=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        finalResult = ''
 
-        host = config.clean_host(host)
-        result = notifiers.nmj_notifier.notify_settings(urllib.unquote_plus(host))
-        if result:
-            return '{"message": "Got settings from %(host)s", "database": "%(database)s", "mount": "%(mount)s"}' % {
-                "host": host, "database": sickbeard.NMJ_DATABASE, "mount": sickbeard.NMJ_MOUNT}
-        else:
-            return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}'
+        if backupFile:
+            source = backupFile
+            target_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
 
+            if helpers.extractZip(source, target_dir):
+                finalResult += "Successfully extracted restore files to " + target_dir
+                finalResult += "<br>Restart sickrage to complete the restore."
+            else:
+                finalResult += "Restore FAILED"
+        else:
+            finalResult += "You need to select a backup file to restore!"
 
-    def testNMJv2(self, host=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        finalResult += "<br />\n"
 
-        host = config.clean_host(host)
-        result = notifiers.nmjv2_notifier.test_notify(urllib.unquote_plus(host))
-        if result:
-            return "Test notice sent successfully to " + urllib.unquote_plus(host)
-        else:
-            return "Test notice failed to " + urllib.unquote_plus(host)
+        return finalResult
 
 
-    def settingsNMJv2(self, host=None, dbloc=None, instance=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+@route('/config/search(/?.*)')
+class ConfigSearch(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigSearch, self).__init__(*args, **kwargs)
 
-        host = config.clean_host(host)
-        result = notifiers.nmjv2_notifier.notify_settings(urllib.unquote_plus(host), dbloc, instance)
-        if result:
-            return '{"message": "NMJ Database found at: %(host)s", "database": "%(database)s"}' % {"host": host,
-                                                                                                   "database": sickbeard.NMJv2_DATABASE}
-        else:
-            return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {
-                "dbloc": dbloc}
+    def index(self):
+        t = PageTemplate(rh=self, file="config_search.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
 
 
-    def testTrakt(self, api=None, username=None, password=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+    def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None,
+                   sab_apikey=None, sab_category=None, sab_category_anime=None, sab_host=None, nzbget_username=None,
+                   nzbget_password=None, nzbget_category=None, nzbget_category_anime=None, nzbget_priority=None,
+                   nzbget_host=None, nzbget_use_https=None, backlog_days=None, backlog_frequency=None,
+                   dailysearch_frequency=None, nzb_method=None, torrent_method=None, usenet_retention=None,
+                   download_propers=None, check_propers_interval=None, allow_high_priority=None,
+                   randomize_providers=None, backlog_startup=None, dailysearch_startup=None,
+                   torrent_dir=None, torrent_username=None, torrent_password=None, torrent_host=None,
+                   torrent_label=None, torrent_label_anime=None, torrent_path=None, torrent_verify_cert=None,
+                   torrent_seed_time=None, torrent_paused=None, torrent_high_bandwidth=None, ignore_words=None,
+                   require_words=None):
 
-        result = notifiers.trakt_notifier.test_notify(api, username, password)
-        if result:
-            return "Test notice sent successfully to Trakt"
-        else:
-            return "Test notice failed to Trakt"
+        results = []
 
+        if not config.change_NZB_DIR(nzb_dir):
+            results += ["Unable to create directory " + os.path.normpath(nzb_dir) + ", dir not changed."]
 
-    def loadShowNotifyLists(self, *args, **kwargs):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        if not config.change_TORRENT_DIR(torrent_dir):
+            results += ["Unable to create directory " + os.path.normpath(torrent_dir) + ", dir not changed."]
 
-        myDB = db.DBConnection()
-        rows = myDB.select("SELECT show_id, show_name, notify_list FROM tv_shows ORDER BY show_name ASC")
+        config.change_DAILYSEARCH_FREQUENCY(dailysearch_frequency)
 
-        data = {}
-        size = 0
-        for r in rows:
-            data[r['show_id']] = {'id': r['show_id'], 'name': r['show_name'], 'list': r['notify_list']}
-            size += 1
-        data['_size'] = size
-        return json.dumps(data)
+        config.change_BACKLOG_FREQUENCY(backlog_frequency)
+        sickbeard.BACKLOG_DAYS = config.to_int(backlog_days, default=7)
 
+        sickbeard.USE_NZBS = config.checkbox_to_value(use_nzbs)
+        sickbeard.USE_TORRENTS = config.checkbox_to_value(use_torrents)
 
-    def testEmail(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.NZB_METHOD = nzb_method
+        sickbeard.TORRENT_METHOD = torrent_method
+        sickbeard.USENET_RETENTION = config.to_int(usenet_retention, default=500)
 
-        host = config.clean_host(host)
-        if notifiers.email_notifier.test_notify(host, port, smtp_from, use_tls, user, pwd, to):
-            return 'Test email sent successfully! Check inbox.'
-        else:
-            return 'ERROR: %s' % notifiers.email_notifier.last_err
+        sickbeard.IGNORE_WORDS = ignore_words if ignore_words else ""
+        sickbeard.REQUIRE_WORDS = require_words if require_words else ""
 
+        sickbeard.RANDOMIZE_PROVIDERS = config.checkbox_to_value(randomize_providers)
 
-    def testNMA(self, nma_api=None, nma_priority=0):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.DOWNLOAD_PROPERS = config.checkbox_to_value(download_propers)
+        sickbeard.CHECK_PROPERS_INTERVAL = check_propers_interval
 
-        result = notifiers.nma_notifier.test_notify(nma_api, nma_priority)
-        if result:
-            return "Test NMA notice sent successfully"
-        else:
-            return "Test NMA notice failed"
+        sickbeard.ALLOW_HIGH_PRIORITY = config.checkbox_to_value(allow_high_priority)
 
+        sickbeard.DAILYSEARCH_STARTUP = config.checkbox_to_value(dailysearch_startup)
+        sickbeard.BACKLOG_STARTUP = config.checkbox_to_value(backlog_startup)
 
-    def testPushalot(self, authorizationToken=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.SAB_USERNAME = sab_username
+        sickbeard.SAB_PASSWORD = sab_password
+        sickbeard.SAB_APIKEY = sab_apikey.strip()
+        sickbeard.SAB_CATEGORY = sab_category
+        sickbeard.SAB_CATEGORY_ANIME = sab_category_anime
+        sickbeard.SAB_HOST = config.clean_url(sab_host)
 
-        result = notifiers.pushalot_notifier.test_notify(authorizationToken)
-        if result:
-            return "Pushalot notification succeeded. Check your Pushalot clients to make sure it worked"
-        else:
-            return "Error sending Pushalot notification"
+        sickbeard.NZBGET_USERNAME = nzbget_username
+        sickbeard.NZBGET_PASSWORD = nzbget_password
+        sickbeard.NZBGET_CATEGORY = nzbget_category
+        sickbeard.NZBGET_CATEGORY_ANIME = nzbget_category_anime
+        sickbeard.NZBGET_HOST = config.clean_host(nzbget_host)
+        sickbeard.NZBGET_USE_HTTPS = config.checkbox_to_value(nzbget_use_https)
+        sickbeard.NZBGET_PRIORITY = config.to_int(nzbget_priority, default=100)
 
+        sickbeard.TORRENT_USERNAME = torrent_username
+        sickbeard.TORRENT_PASSWORD = torrent_password
+        sickbeard.TORRENT_LABEL = torrent_label
+        sickbeard.TORRENT_LABEL_ANIME = torrent_label_anime
+        sickbeard.TORRENT_VERIFY_CERT = config.checkbox_to_value(torrent_verify_cert)
+        sickbeard.TORRENT_PATH = torrent_path
+        sickbeard.TORRENT_SEED_TIME = torrent_seed_time
+        sickbeard.TORRENT_PAUSED = config.checkbox_to_value(torrent_paused)
+        sickbeard.TORRENT_HIGH_BANDWIDTH = config.checkbox_to_value(torrent_high_bandwidth)
+        sickbeard.TORRENT_HOST = config.clean_url(torrent_host)
 
-    def testPushbullet(self, api=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+        sickbeard.save_config()
 
-        result = notifiers.pushbullet_notifier.test_notify(api)
-        if result:
-            return "Pushbullet notification succeeded. Check your device to make sure it worked"
+        if len(results) > 0:
+            for x in results:
+                logger.log(x, logger.ERROR)
+            ui.notifications.error('Error(s) Saving Configuration',
+                                   '<br />\n'.join(results))
         else:
-            return "Error sending Pushbullet notification"
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
+        return self.redirect("/config/search/")
 
-    def getPushbulletDevices(self, api=None):
-        self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
-        result = notifiers.pushbullet_notifier.get_devices(api)
-        if result:
-            return result
-        else:
-            return "Error sending Pushbullet notification"
+@route('/config/postProcessing(/?.*)')
+class ConfigPostProcessing(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigPostProcessing, self).__init__(*args, **kwargs)
 
-    def shutdown(self, pid=None):
+    def index(self):
+        t = PageTemplate(rh=self, file="config_postProcessing.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
 
-        if str(pid) != str(sickbeard.PID):
-            redirect("/home/")
 
-        sickbeard.events.put(sickbeard.events.SystemEvent.SHUTDOWN)
+    def savePostProcessing(self, naming_pattern=None, naming_multi_ep=None,
+                           kodi_data=None, kodi_12plus_data=None, mediabrowser_data=None, sony_ps3_data=None,
+                           wdtv_data=None, tivo_data=None, mede8er_data=None,
+                           keep_processed_dir=None, process_method=None, process_automatically=None,
+                           rename_episodes=None, airdate_episodes=None, unpack=None,
+                           move_associated_files=None, postpone_if_sync_files=None, nfo_rename=None,
+                           tv_download_dir=None, naming_custom_abd=None,
+                           naming_anime=None,
+                           naming_abd_pattern=None, naming_strip_year=None, use_failed_downloads=None,
+                           delete_failed=None, extra_scripts=None, skip_removed_files=None,
+                           naming_custom_sports=None, naming_sports_pattern=None,
+                           naming_custom_anime=None, naming_anime_pattern=None, naming_anime_multi_ep=None,
+                           autopostprocesser_frequency=None):
 
-        title = "Shutting down"
-        message = "SickRage is shutting down..."
+        results = []
 
-        return self._genericMessage(title, message)
+        if not config.change_TV_DOWNLOAD_DIR(tv_download_dir):
+            results += ["Unable to create directory " + os.path.normpath(tv_download_dir) + ", dir not changed."]
 
-    def restart(self, pid=None):
+        sickbeard.PROCESS_AUTOMATICALLY = config.checkbox_to_value(process_automatically)
+        config.change_AUTOPOSTPROCESSER_FREQUENCY(autopostprocesser_frequency)
 
-        if str(pid) != str(sickbeard.PID):
-            redirect("/home/")
+        if sickbeard.PROCESS_AUTOMATICALLY and not sickbeard.autoPostProcesserScheduler.isAlive():
+            sickbeard.autoPostProcesserScheduler.silent = False
+            try:
+                sickbeard.autoPostProcesserScheduler.start()
+            except:
+                pass
+        elif not sickbeard.PROCESS_AUTOMATICALLY:
+            sickbeard.autoPostProcesserScheduler.stop.set()
+            sickbeard.autoPostProcesserScheduler.silent = True
+            try:
+                sickbeard.autoPostProcesserScheduler.join(5)
+            except:
+                pass
 
-        t = PageTemplate(headers=self.request.headers, file="restart.tmpl")
-        t.submenu = HomeMenu()
+        if unpack:
+            if self.isRarSupported() != 'not supported':
+                sickbeard.UNPACK = config.checkbox_to_value(unpack)
+            else:
+                sickbeard.UNPACK = 0
+                results.append("Unpacking Not Supported, disabling unpack setting")
+        else:
+            sickbeard.UNPACK = config.checkbox_to_value(unpack)
 
-        # restart
-        sickbeard.events.put(sickbeard.events.SystemEvent.RESTART)
+        sickbeard.KEEP_PROCESSED_DIR = config.checkbox_to_value(keep_processed_dir)
+        sickbeard.PROCESS_METHOD = process_method
+        sickbeard.EXTRA_SCRIPTS = [x.strip() for x in extra_scripts.split('|') if x.strip()]
+        sickbeard.RENAME_EPISODES = config.checkbox_to_value(rename_episodes)
+        sickbeard.AIRDATE_EPISODES = config.checkbox_to_value(airdate_episodes)
+        sickbeard.MOVE_ASSOCIATED_FILES = config.checkbox_to_value(move_associated_files)
+        sickbeard.POSTPONE_IF_SYNC_FILES = config.checkbox_to_value(postpone_if_sync_files)
+        sickbeard.NAMING_CUSTOM_ABD = config.checkbox_to_value(naming_custom_abd)
+        sickbeard.NAMING_CUSTOM_SPORTS = config.checkbox_to_value(naming_custom_sports)
+        sickbeard.NAMING_CUSTOM_ANIME = config.checkbox_to_value(naming_custom_anime)
+        sickbeard.NAMING_STRIP_YEAR = config.checkbox_to_value(naming_strip_year)
+        sickbeard.USE_FAILED_DOWNLOADS = config.checkbox_to_value(use_failed_downloads)
+        sickbeard.DELETE_FAILED = config.checkbox_to_value(delete_failed)
+        sickbeard.SKIP_REMOVED_FILES = config.checkbox_to_value(skip_removed_files)
+        sickbeard.NFO_RENAME = config.checkbox_to_value(nfo_rename)
 
-        return _munge(t)
+        sickbeard.METADATA_KODI = kodi_data
+        sickbeard.METADATA_KODI_12PLUS = kodi_12plus_data
+        sickbeard.METADATA_MEDIABROWSER = mediabrowser_data
+        sickbeard.METADATA_PS3 = sony_ps3_data
+        sickbeard.METADATA_WDTV = wdtv_data
+        sickbeard.METADATA_TIVO = tivo_data
+        sickbeard.METADATA_MEDE8ER = mede8er_data
 
-    def update(self, pid=None):
+        sickbeard.metadata_provider_dict['KODI'].set_config(sickbeard.METADATA_KODI)
+        sickbeard.metadata_provider_dict['KODI 12+'].set_config(sickbeard.METADATA_KODI_12PLUS)
+        sickbeard.metadata_provider_dict['MediaBrowser'].set_config(sickbeard.METADATA_MEDIABROWSER)
+        sickbeard.metadata_provider_dict['Sony PS3'].set_config(sickbeard.METADATA_PS3)
+        sickbeard.metadata_provider_dict['WDTV'].set_config(sickbeard.METADATA_WDTV)
+        sickbeard.metadata_provider_dict['TIVO'].set_config(sickbeard.METADATA_TIVO)
+        sickbeard.metadata_provider_dict['Mede8er'].set_config(sickbeard.METADATA_MEDE8ER)
 
-        if str(pid) != str(sickbeard.PID):
-            redirect("/home/")
+        if self.isNamingValid(naming_pattern, naming_multi_ep, anime_type=naming_anime) != "invalid":
+            sickbeard.NAMING_PATTERN = naming_pattern
+            sickbeard.NAMING_MULTI_EP = int(naming_multi_ep)
+            sickbeard.NAMING_ANIME = int(naming_anime)
+            sickbeard.NAMING_FORCE_FOLDERS = naming.check_force_season_folders()
+        else:
+            if int(naming_anime) in [1, 2]:
+                results.append("You tried saving an invalid anime naming config, not saving your naming settings")
+            else:
+                results.append("You tried saving an invalid naming config, not saving your naming settings")
 
-        updated = sickbeard.versionCheckScheduler.action.update()  # @UndefinedVariable
-        if updated:
-            # do a hard restart
-            sickbeard.events.put(sickbeard.events.SystemEvent.RESTART)
+        if self.isNamingValid(naming_anime_pattern, naming_anime_multi_ep, anime_type=naming_anime) != "invalid":
+            sickbeard.NAMING_ANIME_PATTERN = naming_anime_pattern
+            sickbeard.NAMING_ANIME_MULTI_EP = int(naming_anime_multi_ep)
+            sickbeard.NAMING_ANIME = int(naming_anime)
+            sickbeard.NAMING_FORCE_FOLDERS = naming.check_force_season_folders()
+        else:
+            if int(naming_anime) in [1, 2]:
+                results.append("You tried saving an invalid anime naming config, not saving your naming settings")
+            else:
+                results.append("You tried saving an invalid naming config, not saving your naming settings")
 
-            t = PageTemplate(headers=self.request.headers, file="restart_bare.tmpl")
-            return _munge(t)
+        if self.isNamingValid(naming_abd_pattern, None, abd=True) != "invalid":
+            sickbeard.NAMING_ABD_PATTERN = naming_abd_pattern
         else:
-            return self._genericMessage("Update Failed",
-                                        "Update wasn't successful, not restarting. Check your log for more information.")
+            results.append(
+                "You tried saving an invalid air-by-date naming config, not saving your air-by-date settings")
 
-    def branchCheckout(self, branch):
-        sickbeard.BRANCH = branch
-        ui.notifications.message('Checking out branch: ', branch)
-        return self.update(sickbeard.PID)
+        if self.isNamingValid(naming_sports_pattern, None, sports=True) != "invalid":
+            sickbeard.NAMING_SPORTS_PATTERN = naming_sports_pattern
+        else:
+            results.append(
+                "You tried saving an invalid sports naming config, not saving your sports settings")
 
-    def displayShow(self, show=None):
+        sickbeard.save_config()
 
-        if show is None:
-            return self._genericMessage("Error", "Invalid show ID")
+        if len(results) > 0:
+            for x in results:
+                logger.log(x, logger.ERROR)
+            ui.notifications.error('Error(s) Saving Configuration',
+                                   '<br />\n'.join(results))
         else:
-            showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
-            if showObj is None:
-                return self._genericMessage("Error", "Show not in show list")
+        return self.redirect("/config/postProcessing/")
 
-        myDB = db.DBConnection()
-        seasonResults = myDB.select(
-            "SELECT DISTINCT season FROM tv_episodes WHERE showid = ? ORDER BY season desc",
-            [showObj.indexerid]
-        )
+    def testNaming(self, pattern=None, multi=None, abd=False, sports=False, anime_type=None):
 
-        sqlResults = myDB.select(
-            "SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC",
-            [showObj.indexerid]
-        )
+        if multi is not None:
+            multi = int(multi)
 
-        t = PageTemplate(headers=self.request.headers, file="displayShow.tmpl")
-        t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}]
+        if anime_type is not None:
+            anime_type = int(anime_type)
 
-        try:
-            t.showLoc = (showObj.location, True)
-        except sickbeard.exceptions.ShowDirNotFoundException:
-            t.showLoc = (showObj._location, False)
+        result = naming.test_name(pattern, multi, abd, sports, anime_type)
 
-        show_message = ''
+        result = ek.ek(os.path.join, result['dir'], result['name'])
 
-        if sickbeard.showQueueScheduler.action.isBeingAdded(showObj):  # @UndefinedVariable
-            show_message = 'This show is in the process of being downloaded - the info below is incomplete.'
+        return result
 
-        elif sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):  # @UndefinedVariable
-            show_message = 'The information on this page is in the process of being updated.'
 
-        elif sickbeard.showQueueScheduler.action.isBeingRefreshed(showObj):  # @UndefinedVariable
-            show_message = 'The episodes below are currently being refreshed from disk'
+    def isNamingValid(self, pattern=None, multi=None, abd=False, sports=False, anime_type=None):
+        if pattern is None:
+            return "invalid"
 
-        elif sickbeard.showQueueScheduler.action.isBeingSubtitled(showObj):  # @UndefinedVariable
-            show_message = 'Currently downloading subtitles for this show'
+        if multi is not None:
+            multi = int(multi)
 
-        elif sickbeard.showQueueScheduler.action.isInRefreshQueue(showObj):  # @UndefinedVariable
-            show_message = 'This show is queued to be refreshed.'
+        if anime_type is not None:
+            anime_type = int(anime_type)
 
-        elif sickbeard.showQueueScheduler.action.isInUpdateQueue(showObj):  # @UndefinedVariable
-            show_message = 'This show is queued and awaiting an update.'
+        # air by date shows just need one check, we don't need to worry about season folders
+        if abd:
+            is_valid = naming.check_valid_abd_naming(pattern)
+            require_season_folders = False
 
-        elif sickbeard.showQueueScheduler.action.isInSubtitleQueue(showObj):  # @UndefinedVariable
-            show_message = 'This show is queued and awaiting subtitles download.'
+        # sport shows just need one check, we don't need to worry about season folders
+        elif sports:
+            is_valid = naming.check_valid_sports_naming(pattern)
+            require_season_folders = False
 
-        if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj):  # @UndefinedVariable
-            if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):  # @UndefinedVariable
-                t.submenu.append(
-                    {'title': 'Remove', 'path': 'home/deleteShow?show=%d' % showObj.indexerid, 'confirm': True})
-                t.submenu.append({'title': 'Re-scan files', 'path': 'home/refreshShow?show=%d' % showObj.indexerid})
-                t.submenu.append(
-                    {'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&amp;force=1' % showObj.indexerid})
-                t.submenu.append({'title': 'Update show in XBMC',
-                                  'path': 'home/updateXBMC?showName=%s' % urllib.quote_plus(
-                                      showObj.name.encode('utf-8')), 'requires': haveXBMC})
-                t.submenu.append({'title': 'Preview Rename', 'path': 'home/testRename?show=%d' % showObj.indexerid})
-                if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled(
-                        showObj) and showObj.subtitles:
-                    t.submenu.append(
-                        {'title': 'Download Subtitles', 'path': 'home/subtitleShow?show=%d' % showObj.indexerid})
+        else:
+            # check validity of single and multi ep cases for the whole path
+            is_valid = naming.check_valid_naming(pattern, multi, anime_type)
 
-        t.show = showObj
-        t.sqlResults = sqlResults
-        t.seasonResults = seasonResults
-        t.show_message = show_message
+            # check validity of single and multi ep cases for only the file name
+            require_season_folders = naming.check_force_season_folders(pattern, multi, anime_type)
 
-        epCounts = {}
-        epCats = {}
-        epCounts[Overview.SKIPPED] = 0
-        epCounts[Overview.WANTED] = 0
-        epCounts[Overview.QUAL] = 0
-        epCounts[Overview.GOOD] = 0
-        epCounts[Overview.UNAIRED] = 0
-        epCounts[Overview.SNATCHED] = 0
+        if is_valid and not require_season_folders:
+            return "valid"
+        elif is_valid and require_season_folders:
+            return "seasonfolders"
+        else:
+            return "invalid"
 
-        for curResult in sqlResults:
-            curEpCat = showObj.getOverview(int(curResult["status"]))
-            if curEpCat:
-                epCats[str(curResult["season"]) + "x" + str(curResult["episode"])] = curEpCat
-                epCounts[curEpCat] += 1
 
-        def titler(x):
-            if not x or sickbeard.SORT_ARTICLE:
-                return x
-            if x.lower().startswith('a '):
-                x = x[2:]
-            if x.lower().startswith('an '):
-                x = x[3:]
-            elif x.lower().startswith('the '):
-                x = x[4:]
-            return x
+    def isRarSupported(self):
+        """
+        Test Packing Support:
+            - Simulating in memory rar extraction on test.rar file
+        """
 
-        if sickbeard.ANIME_SPLIT_HOME:
-            shows = []
-            anime = []
-            for show in sickbeard.showList:
-                if show.is_anime:
-                    anime.append(show)
-                else:
-                    shows.append(show)
-            t.sortedShowLists = [["Shows", sorted(shows, lambda x, y: cmp(titler(x.name), titler(y.name)))],
-                                 ["Anime", sorted(anime, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
-        else:
-            t.sortedShowLists = [
-                ["Shows", sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
+        try:
+            rar_path = os.path.join(sickbeard.PROG_DIR, 'lib', 'unrar2', 'test.rar')
+            testing = RarFile(rar_path).read_files('*test.txt')
+            if testing[0][1] == 'This is only a test.':
+                return 'supported'
+            logger.log(u'Rar Not Supported: Can not read the content of test file', logger.ERROR)
+            return 'not supported'
+        except Exception, e:
+            logger.log(u'Rar Not Supported: ' + ex(e), logger.ERROR)
+            return 'not supported'
 
-        t.bwl = None
-        if showObj.is_anime:
-            t.bwl = BlackAndWhiteList(showObj.indexerid)
 
-        t.epCounts = epCounts
-        t.epCats = epCats
+@route('/config/providers(/?.*)')
+class ConfigProviders(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigProviders, self).__init__(*args, **kwargs)
 
-        showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid)
+    def index(self):
+        t = PageTemplate(rh=self, file="config_providers.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
 
-        indexerid = int(showObj.indexerid)
-        indexer = int(showObj.indexer)
-        t.all_scene_exceptions = showObj.exceptions
-        t.scene_numbering = get_scene_numbering_for_show(indexerid, indexer)
-        t.xem_numbering = get_xem_numbering_for_show(indexerid, indexer)
-        t.scene_absolute_numbering = get_scene_absolute_numbering_for_show(indexerid, indexer)
-        t.xem_absolute_numbering = get_xem_absolute_numbering_for_show(indexerid, indexer)
 
-        return _munge(t)
+    def canAddNewznabProvider(self, name):
 
+        if not name:
+            return json.dumps({'error': 'No Provider Name specified'})
 
-    def plotDetails(self, show, season, episode):
-        myDB = db.DBConnection()
-        result = myDB.selectOne(
-            "SELECT description FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?",
-            (int(show), int(season), int(episode)))
-        return result['description'] if result else 'Episode not found.'
+        providerDict = dict(zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
 
+        tempProvider = newznab.NewznabProvider(name, '')
 
-    def sceneExceptions(self, show):
-        exceptionsList = sickbeard.scene_exceptions.get_all_scene_exceptions(show)
-        if not exceptionsList:
-            return "No scene exceptions"
+        if tempProvider.getID() in providerDict:
+            return json.dumps({'error': 'Provider Name already exists as ' + providerDict[tempProvider.getID()].name})
+        else:
+            return json.dumps({'success': tempProvider.getID()})
 
-        out = []
-        for season, names in iter(sorted(exceptionsList.iteritems())):
-            if season == -1:
-                season = "*"
-            out.append("S" + str(season) + ": " + ", ".join(names))
-        return "<br/>".join(out)
 
+    def saveNewznabProvider(self, name, url, key=''):
 
-    def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[],
-                 flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None,
-                 indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None,
-                 rls_require_words=None, anime=None, blackWords=None, whiteWords=None, blacklist=None, whitelist=None,
-                 scene=None, defaultEpStatus=None):
+        if not name or not url:
+            return '0'
 
-        if show is None:
-            errString = "Invalid show ID: " + str(show)
-            if directCall:
-                return [errString]
-            else:
-                return self._genericMessage("Error", errString)
+        providerDict = dict(zip([x.name for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+        if name in providerDict:
+            if not providerDict[name].default:
+                providerDict[name].name = name
+                providerDict[name].url = config.clean_url(url)
 
-        if not showObj:
-            errString = "Unable to find the specified show: " + str(show)
-            if directCall:
-                return [errString]
+            providerDict[name].key = key
+            # a 0 in the key spot indicates that no key is needed
+            if key == '0':
+                providerDict[name].needs_auth = False
             else:
-                return self._genericMessage("Error", errString)
+                providerDict[name].needs_auth = True
 
-        showObj.exceptions = scene_exceptions.get_scene_exceptions(showObj.indexerid)
+            return providerDict[name].getID() + '|' + providerDict[name].configStr()
 
-        if not location and not anyQualities and not bestQualities and not flatten_folders:
-            t = PageTemplate(headers=self.request.headers, file="editShow.tmpl")
-            t.submenu = HomeMenu()
+        else:
+            newProvider = newznab.NewznabProvider(name, url, key=key)
+            sickbeard.newznabProviderList.append(newProvider)
+            return newProvider.getID() + '|' + newProvider.configStr()
 
-            if showObj.is_anime:
-                bwl = BlackAndWhiteList(showObj.indexerid)
+    def getNewznabCategories(self, name, url, key):
+        '''
+        Retrieves a list of possible categories with category id's
+        Using the default url/api?cat
+        http://yournewznaburl.com/api?t=caps&apikey=yourapikey
+        '''
+        error = ""
+        success = False
 
-                t.whiteWords = ""
-                if "global" in bwl.whiteDict:
-                    t.whiteWords = ", ".join(bwl.whiteDict["global"])
+        if not name:
+            error += "\nNo Provider Name specified"
+        if not url:
+            error += "\nNo Provider Url specified"
+        if not key:
+            error += "\nNo Provider Api key specified"
 
-                t.blackWords = ""
-                if "global" in bwl.blackDict:
-                    t.blackWords = ", ".join(bwl.blackDict["global"])
+        if error <> "":
+            return json.dumps({'success': False, 'error': error})
 
-                t.whitelist = []
-                if bwl.whiteDict.has_key("release_group"):
-                    t.whitelist = bwl.whiteDict["release_group"]
+        # Get list with Newznabproviders
+        # providerDict = dict(zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
 
-                t.blacklist = []
-                if bwl.blackDict.has_key("release_group"):
-                    t.blacklist = bwl.blackDict["release_group"]
+        # Get newznabprovider obj with provided name
+        tempProvider = newznab.NewznabProvider(name, url, key)
 
-                t.groups = []
-                if helpers.set_up_anidb_connection():
-                    anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=showObj.name)
-                    t.groups = anime.get_groups()
+        success, tv_categories, error = tempProvider.get_newznab_categories()
+
+        return json.dumps({'success': success, 'tv_categories': tv_categories, 'error': error})
+
+    def deleteNewznabProvider(self, nnid):
 
-            with showObj.lock:
-                t.show = showObj
-                t.scene_exceptions = get_scene_exceptions(showObj.indexerid)
+        providerDict = dict(zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
 
-            return _munge(t)
+        if nnid not in providerDict or providerDict[nnid].default:
+            return '0'
 
-        flatten_folders = config.checkbox_to_value(flatten_folders)
-        dvdorder = config.checkbox_to_value(dvdorder)
-        archive_firstmatch = config.checkbox_to_value(archive_firstmatch)
-        paused = config.checkbox_to_value(paused)
-        air_by_date = config.checkbox_to_value(air_by_date)
-        scene = config.checkbox_to_value(scene)
-        sports = config.checkbox_to_value(sports)
-        anime = config.checkbox_to_value(anime)
-        subtitles = config.checkbox_to_value(subtitles)
+        # delete it from the list
+        sickbeard.newznabProviderList.remove(providerDict[nnid])
 
-        if indexerLang and indexerLang in sickbeard.indexerApi(showObj.indexer).indexer().config['valid_languages']:
-            indexer_lang = indexerLang
-        else:
-            indexer_lang = showObj.lang
+        if nnid in sickbeard.PROVIDER_ORDER:
+            sickbeard.PROVIDER_ORDER.remove(nnid)
 
-        # if we changed the language then kick off an update
-        if indexer_lang == showObj.lang:
-            do_update = False
-        else:
-            do_update = True
+        return '1'
 
-        if scene == showObj.scene and anime == showObj.anime:
-            do_update_scene_numbering = False
-        else:
-            do_update_scene_numbering = True
 
-        if type(anyQualities) != list:
-            anyQualities = [anyQualities]
+    def canAddTorrentRssProvider(self, name, url, cookies):
 
-        if type(bestQualities) != list:
-            bestQualities = [bestQualities]
+        if not name:
+            return json.dumps({'error': 'Invalid name specified'})
 
-        if type(exceptions_list) != list:
-            exceptions_list = [exceptions_list]
+        providerDict = dict(
+            zip([x.getID() for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
 
-        # If directCall from mass_edit_update no scene exceptions handling or blackandwhite list handling
-        if directCall:
-            do_update_exceptions = False
+        tempProvider = rsstorrent.TorrentRssProvider(name, url, cookies)
+
+        if tempProvider.getID() in providerDict:
+            return json.dumps({'error': 'Exists as ' + providerDict[tempProvider.getID()].name})
         else:
-            if set(exceptions_list) == set(showObj.exceptions):
-                do_update_exceptions = False
+            (succ, errMsg) = tempProvider.validateRSS()
+            if succ:
+                return json.dumps({'success': tempProvider.getID()})
             else:
-                do_update_exceptions = True
+                return json.dumps({'error': errMsg})
 
-            if showObj.is_anime:
-                bwl = BlackAndWhiteList(showObj.indexerid)
-                if whitelist:
-                    whitelist = whitelist.split(",")
-                    shortWhiteList = []
-                    if helpers.set_up_anidb_connection():
-                        for groupName in whitelist:
-                            group = sickbeard.ADBA_CONNECTION.group(gname=groupName)
-                            for line in group.datalines:
-                                if line["shortname"]:
-                                    shortWhiteList.append(line["shortname"])
-                            else:
-                                if not groupName in shortWhiteList:
-                                    shortWhiteList.append(groupName)
-                    else:
-                        shortWhiteList = whitelist
-                    bwl.set_white_keywords_for("release_group", shortWhiteList)
-                else:
-                    bwl.set_white_keywords_for("release_group", [])
 
-                if blacklist:
-                    blacklist = blacklist.split(",")
-                    shortBlacklist = []
-                    if helpers.set_up_anidb_connection():
-                        for groupName in blacklist:
-                            group = sickbeard.ADBA_CONNECTION.group(gname=groupName)
-                            for line in group.datalines:
-                                if line["shortname"]:
-                                    shortBlacklist.append(line["shortname"])
-                            else:
-                                if not groupName in shortBlacklist:
-                                    shortBlacklist.append(groupName)
-                    else:
-                        shortBlacklist = blacklist
-                    bwl.set_black_keywords_for("release_group", shortBlacklist)
-                else:
-                    bwl.set_black_keywords_for("release_group", [])
+    def saveTorrentRssProvider(self, name, url, cookies):
 
-                if whiteWords:
-                    whiteWords = [x.strip() for x in whiteWords.split(",")]
-                    bwl.set_white_keywords_for("global", whiteWords)
-                else:
-                    bwl.set_white_keywords_for("global", [])
+        if not name or not url:
+            return '0'
 
-                if blackWords:
-                    blackWords = [x.strip() for x in blackWords.split(",")]
-                    bwl.set_black_keywords_for("global", blackWords)
-                else:
-                    bwl.set_black_keywords_for("global", [])
+        providerDict = dict(zip([x.name for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
 
-        errors = []
-        with showObj.lock:
-            newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
-            showObj.quality = newQuality
-            showObj.archive_firstmatch = archive_firstmatch
+        if name in providerDict:
+            providerDict[name].name = name
+            providerDict[name].url = config.clean_url(url)
+            providerDict[name].cookies = cookies
 
-            # reversed for now
-            if bool(showObj.flatten_folders) != bool(flatten_folders):
-                showObj.flatten_folders = flatten_folders
-                try:
-                    sickbeard.showQueueScheduler.action.refreshShow(showObj)  # @UndefinedVariable
-                except exceptions.CantRefreshException, e:
-                    errors.append("Unable to refresh this show: " + ex(e))
+            return providerDict[name].getID() + '|' + providerDict[name].configStr()
 
-            showObj.paused = paused
-            showObj.scene = scene
-            showObj.anime = anime
-            showObj.sports = sports
-            showObj.subtitles = subtitles
-            showObj.air_by_date = air_by_date
+        else:
+            newProvider = rsstorrent.TorrentRssProvider(name, url, cookies)
+            sickbeard.torrentRssProviderList.append(newProvider)
+            return newProvider.getID() + '|' + newProvider.configStr()
 
-            if not directCall:
-                showObj.lang = indexer_lang
-                showObj.dvdorder = dvdorder
-                showObj.rls_ignore_words = rls_ignore_words.strip()
-                showObj.rls_require_words = rls_require_words.strip()
-                showObj.default_ep_status = defaultEpStatus
 
-            # if we change location clear the db of episodes, change it, write to db, and rescan
-            if os.path.normpath(showObj._location) != os.path.normpath(location):
-                logger.log(os.path.normpath(showObj._location) + " != " + os.path.normpath(location), logger.DEBUG)
-                if not ek.ek(os.path.isdir, location) and not sickbeard.CREATE_MISSING_SHOW_DIRS:
-                    errors.append("New location <tt>%s</tt> does not exist" % location)
+    def deleteTorrentRssProvider(self, id):
 
-                # don't bother if we're going to update anyway
-                elif not do_update:
-                    # change it
-                    try:
-                        showObj.location = location
-                        try:
-                            sickbeard.showQueueScheduler.action.refreshShow(showObj)  # @UndefinedVariable
-                        except exceptions.CantRefreshException, e:
-                            errors.append("Unable to refresh this show:" + ex(e))
-                            # grab updated info from TVDB
-                            # showObj.loadEpisodesFromIndexer()
-                            # rescan the episodes in the new folder
-                    except exceptions.NoNFOException:
-                        errors.append(
-                            "The folder at <tt>%s</tt> doesn't contain a tvshow.nfo - copy your files to that folder before you change the directory in SickRage." % location)
+        providerDict = dict(
+            zip([x.getID() for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
 
-            # save it to the DB
-            showObj.saveToDB()
+        if id not in providerDict:
+            return '0'
 
-        # force the update
-        if do_update:
-            try:
-                sickbeard.showQueueScheduler.action.updateShow(showObj, True)  # @UndefinedVariable
-                time.sleep(cpu_presets[sickbeard.CPU_PRESET])
-            except exceptions.CantUpdateException, e:
-                errors.append("Unable to force an update on the show.")
+        # delete it from the list
+        sickbeard.torrentRssProviderList.remove(providerDict[id])
 
-        if do_update_exceptions:
-            try:
-                scene_exceptions.update_scene_exceptions(showObj.indexerid, exceptions_list)  # @UndefinedVdexerid)
-                time.sleep(cpu_presets[sickbeard.CPU_PRESET])
-            except exceptions.CantUpdateException, e:
-                errors.append("Unable to force an update on scene exceptions of the show.")
+        if id in sickbeard.PROVIDER_ORDER:
+            sickbeard.PROVIDER_ORDER.remove(id)
 
-        if do_update_scene_numbering:
-            try:
-                sickbeard.scene_numbering.xem_refresh(showObj.indexerid, showObj.indexer)  # @UndefinedVariable
-                time.sleep(cpu_presets[sickbeard.CPU_PRESET])
-            except exceptions.CantUpdateException, e:
-                errors.append("Unable to force an update on scene numbering of the show.")
+        return '1'
 
-        if directCall:
-            return errors
 
-        if len(errors) > 0:
-            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>")
+    def saveProviders(self, newznab_string='', torrentrss_string='', provider_order=None, **kwargs):
 
-        redirect("/home/displayShow?show=" + show)
+        results = []
 
+        provider_str_list = provider_order.split()
+        provider_list = []
 
-    def deleteShow(self, show=None, full=0):
+        newznabProviderDict = dict(
+            zip([x.getID() for x in sickbeard.newznabProviderList], sickbeard.newznabProviderList))
 
-        if show is None:
-            return self._genericMessage("Error", "Invalid show ID")
+        finishedNames = []
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+        # add all the newznab info we got into our list
+        if newznab_string:
+            for curNewznabProviderStr in newznab_string.split('!!!'):
 
-        if showObj is None:
-            return self._genericMessage("Error", "Unable to find the specified show")
+                if not curNewznabProviderStr:
+                    continue
 
-        if sickbeard.showQueueScheduler.action.isBeingAdded(
-                showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj):  # @UndefinedVariable
-            return self._genericMessage("Error", "Shows can't be deleted while they're being added or updated.")
+                cur_name, cur_url, cur_key, cur_cat = curNewznabProviderStr.split('|')
+                cur_url = config.clean_url(cur_url)
 
-        if sickbeard.USE_TRAKT and sickbeard.TRAKT_SYNC:
-            # remove show from trakt.tv library
-            sickbeard.traktCheckerScheduler.action.removeShowFromTraktLibrary(showObj)
+                newProvider = newznab.NewznabProvider(cur_name, cur_url, key=cur_key)
 
-        showObj.deleteShow(bool(full))
+                cur_id = newProvider.getID()
 
-        ui.notifications.message('<b>%s</b> has been %s %s' %
-                                 (showObj.name,
-                                  ('deleted', 'trashed')[sickbeard.TRASH_REMOVE_SHOW],
-                                  ('(media untouched)', '(with all related media)')[bool(full)]))
-        redirect("/home/")
+                # if it already exists then update it
+                if cur_id in newznabProviderDict:
+                    newznabProviderDict[cur_id].name = cur_name
+                    newznabProviderDict[cur_id].url = cur_url
+                    newznabProviderDict[cur_id].key = cur_key
+                    newznabProviderDict[cur_id].catIDs = cur_cat
+                    # a 0 in the key spot indicates that no key is needed
+                    if cur_key == '0':
+                        newznabProviderDict[cur_id].needs_auth = False
+                    else:
+                        newznabProviderDict[cur_id].needs_auth = True
+
+                    try:
+                        newznabProviderDict[cur_id].search_mode = str(kwargs[cur_id + '_search_mode']).strip()
+                    except:
+                        pass
 
+                    try:
+                        newznabProviderDict[cur_id].search_fallback = config.checkbox_to_value(
+                            kwargs[cur_id + '_search_fallback'])
+                    except:
+                        newznabProviderDict[cur_id].search_fallback = 0
 
-    def refreshShow(self, show=None):
+                    try:
+                        newznabProviderDict[cur_id].enable_daily = config.checkbox_to_value(
+                            kwargs[cur_id + '_enable_daily'])
+                    except:
+                        newznabProviderDict[cur_id].enable_daily = 0
 
-        if show is None:
-            return self._genericMessage("Error", "Invalid show ID")
+                    try:
+                        newznabProviderDict[cur_id].enable_backlog = config.checkbox_to_value(
+                            kwargs[cur_id + '_enable_backlog'])
+                    except:
+                        newznabProviderDict[cur_id].enable_backlog = 0
+                else:
+                    sickbeard.newznabProviderList.append(newProvider)
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+                finishedNames.append(cur_id)
 
-        if showObj is None:
-            return self._genericMessage("Error", "Unable to find the specified show")
+        # delete anything that is missing
+        for curProvider in sickbeard.newznabProviderList:
+            if curProvider.getID() not in finishedNames:
+                sickbeard.newznabProviderList.remove(curProvider)
 
-        # force the update from the DB
-        try:
-            sickbeard.showQueueScheduler.action.refreshShow(showObj)  # @UndefinedVariable
-        except exceptions.CantRefreshException, e:
-            ui.notifications.error("Unable to refresh this show.",
-                                   ex(e))
+        torrentRssProviderDict = dict(
+            zip([x.getID() for x in sickbeard.torrentRssProviderList], sickbeard.torrentRssProviderList))
+        finishedNames = []
 
-        time.sleep(cpu_presets[sickbeard.CPU_PRESET])
+        if torrentrss_string:
+            for curTorrentRssProviderStr in torrentrss_string.split('!!!'):
 
-        redirect("/home/displayShow?show=" + str(showObj.indexerid))
+                if not curTorrentRssProviderStr:
+                    continue
 
+                curName, curURL, curCookies = curTorrentRssProviderStr.split('|')
+                curURL = config.clean_url(curURL)
 
-    def updateShow(self, show=None, force=0):
+                newProvider = rsstorrent.TorrentRssProvider(curName, curURL, curCookies)
 
-        if show is None:
-            return self._genericMessage("Error", "Invalid show ID")
+                curID = newProvider.getID()
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+                # if it already exists then update it
+                if curID in torrentRssProviderDict:
+                    torrentRssProviderDict[curID].name = curName
+                    torrentRssProviderDict[curID].url = curURL
+                    torrentRssProviderDict[curID].cookies = curCookies
+                else:
+                    sickbeard.torrentRssProviderList.append(newProvider)
 
-        if showObj is None:
-            return self._genericMessage("Error", "Unable to find the specified show")
+                finishedNames.append(curID)
 
-        # force the update
-        try:
-            sickbeard.showQueueScheduler.action.updateShow(showObj, bool(force))  # @UndefinedVariable
-        except exceptions.CantUpdateException, e:
-            ui.notifications.error("Unable to update this show.",
-                                   ex(e))
+        # delete anything that is missing
+        for curProvider in sickbeard.torrentRssProviderList:
+            if curProvider.getID() not in finishedNames:
+                sickbeard.torrentRssProviderList.remove(curProvider)
 
-        # just give it some time
-        time.sleep(cpu_presets[sickbeard.CPU_PRESET])
+        # do the enable/disable
+        for curProviderStr in provider_str_list:
+            curProvider, curEnabled = curProviderStr.split(':')
+            curEnabled = config.to_int(curEnabled)
 
-        redirect("/home/displayShow?show=" + str(showObj.indexerid))
+            curProvObj = [x for x in sickbeard.providers.sortedProviderList() if
+                          x.getID() == curProvider and hasattr(x, 'enabled')]
+            if curProvObj:
+                curProvObj[0].enabled = bool(curEnabled)
 
+            provider_list.append(curProvider)
+            if curProvider in newznabProviderDict:
+                newznabProviderDict[curProvider].enabled = bool(curEnabled)
+            elif curProvider in torrentRssProviderDict:
+                torrentRssProviderDict[curProvider].enabled = bool(curEnabled)
 
-    def subtitleShow(self, show=None, force=0):
+        # dynamically load provider settings
+        for curTorrentProvider in [curProvider for curProvider in sickbeard.providers.sortedProviderList() if
+                                   curProvider.providerType == sickbeard.GenericProvider.TORRENT]:
 
-        if show is None:
-            return self._genericMessage("Error", "Invalid show ID")
+            if hasattr(curTorrentProvider, 'minseed'):
+                try:
+                    curTorrentProvider.minseed = int(str(kwargs[curTorrentProvider.getID() + '_minseed']).strip())
+                except:
+                    curTorrentProvider.minseed = 0
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+            if hasattr(curTorrentProvider, 'minleech'):
+                try:
+                    curTorrentProvider.minleech = int(str(kwargs[curTorrentProvider.getID() + '_minleech']).strip())
+                except:
+                    curTorrentProvider.minleech = 0
 
-        if showObj is None:
-            return self._genericMessage("Error", "Unable to find the specified show")
+            if hasattr(curTorrentProvider, 'ratio'):
+                try:
+                    curTorrentProvider.ratio = str(kwargs[curTorrentProvider.getID() + '_ratio']).strip()
+                except:
+                    curTorrentProvider.ratio = None
 
-        # search and download subtitles
-        sickbeard.showQueueScheduler.action.downloadSubtitles(showObj, bool(force))  # @UndefinedVariable
+            if hasattr(curTorrentProvider, 'digest'):
+                try:
+                    curTorrentProvider.digest = str(kwargs[curTorrentProvider.getID() + '_digest']).strip()
+                except:
+                    curTorrentProvider.digest = None
 
-        time.sleep(cpu_presets[sickbeard.CPU_PRESET])
+            if hasattr(curTorrentProvider, 'hash'):
+                try:
+                    curTorrentProvider.hash = str(kwargs[curTorrentProvider.getID() + '_hash']).strip()
+                except:
+                    curTorrentProvider.hash = None
 
-        redirect("/home/displayShow?show=" + str(showObj.indexerid))
+            if hasattr(curTorrentProvider, 'api_key'):
+                try:
+                    curTorrentProvider.api_key = str(kwargs[curTorrentProvider.getID() + '_api_key']).strip()
+                except:
+                    curTorrentProvider.api_key = None
 
+            if hasattr(curTorrentProvider, 'username'):
+                try:
+                    curTorrentProvider.username = str(kwargs[curTorrentProvider.getID() + '_username']).strip()
+                except:
+                    curTorrentProvider.username = None
 
-    def updateXBMC(self, showName=None):
+            if hasattr(curTorrentProvider, 'password'):
+                try:
+                    curTorrentProvider.password = str(kwargs[curTorrentProvider.getID() + '_password']).strip()
+                except:
+                    curTorrentProvider.password = None
 
-        # only send update to first host in the list -- workaround for xbmc sql backend users
-        if sickbeard.XBMC_UPDATE_ONLYFIRST:
-            # only send update to first host in the list -- workaround for xbmc sql backend users
-            host = sickbeard.XBMC_HOST.split(",")[0].strip()
-        else:
-            host = sickbeard.XBMC_HOST
+            if hasattr(curTorrentProvider, 'passkey'):
+                try:
+                    curTorrentProvider.passkey = str(kwargs[curTorrentProvider.getID() + '_passkey']).strip()
+                except:
+                    curTorrentProvider.passkey = None
 
-        if notifiers.xbmc_notifier.update_library(showName=showName):
-            ui.notifications.message("Library update command sent to XBMC host(s): " + host)
-        else:
-            ui.notifications.error("Unable to contact one or more XBMC host(s): " + host)
-        redirect('/home/')
+            if hasattr(curTorrentProvider, 'confirmed'):
+                try:
+                    curTorrentProvider.confirmed = config.checkbox_to_value(
+                        kwargs[curTorrentProvider.getID() + '_confirmed'])
+                except:
+                    curTorrentProvider.confirmed = 0
 
+            if hasattr(curTorrentProvider, 'proxy'):
+                try:
+                    curTorrentProvider.proxy.enabled = config.checkbox_to_value(
+                        kwargs[curTorrentProvider.getID() + '_proxy'])
+                except:
+                    curTorrentProvider.proxy.enabled = 0
 
-    def updatePLEX(self, *args, **kwargs):
-        if notifiers.plex_notifier.update_library():
-            ui.notifications.message(
-                "Library update command sent to Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST)
-        else:
-            ui.notifications.error("Unable to contact Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST)
-        redirect('/home/')
+                if hasattr(curTorrentProvider.proxy, 'url'):
+                    try:
+                        curTorrentProvider.proxy.url = str(kwargs[curTorrentProvider.getID() + '_proxy_url']).strip()
+                    except:
+                        curTorrentProvider.proxy.url = None
 
+            if hasattr(curTorrentProvider, 'freeleech'):
+                try:
+                    curTorrentProvider.freeleech = config.checkbox_to_value(
+                        kwargs[curTorrentProvider.getID() + '_freeleech'])
+                except:
+                    curTorrentProvider.freeleech = 0
 
-    def setStatus(self, show=None, eps=None, status=None, direct=False):
+            if hasattr(curTorrentProvider, 'search_mode'):
+                try:
+                    curTorrentProvider.search_mode = str(kwargs[curTorrentProvider.getID() + '_search_mode']).strip()
+                except:
+                    curTorrentProvider.search_mode = 'eponly'
 
-        if show is None or eps is None or status is None:
-            errMsg = "You must specify a show and at least one episode"
-            if direct:
-                ui.notifications.error('Error', errMsg)
-                return json.dumps({'result': 'error'})
-            else:
-                return self._genericMessage("Error", errMsg)
+            if hasattr(curTorrentProvider, 'search_fallback'):
+                try:
+                    curTorrentProvider.search_fallback = config.checkbox_to_value(
+                        kwargs[curTorrentProvider.getID() + '_search_fallback'])
+                except:
+                    curTorrentProvider.search_fallback = 0  # these exceptions are catching unselected checkboxes
 
-        if not statusStrings.has_key(int(status)):
-            errMsg = "Invalid status"
-            if direct:
-                ui.notifications.error('Error', errMsg)
-                return json.dumps({'result': 'error'})
-            else:
-                return self._genericMessage("Error", errMsg)
+            if hasattr(curTorrentProvider, 'enable_daily'):
+                try:
+                    curTorrentProvider.enable_daily = config.checkbox_to_value(
+                        kwargs[curTorrentProvider.getID() + '_enable_daily'])
+                except:
+                    curTorrentProvider.enable_daily = 0  # these exceptions are actually catching unselected checkboxes
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+            if hasattr(curTorrentProvider, 'enable_backlog'):
+                try:
+                    curTorrentProvider.enable_backlog = config.checkbox_to_value(
+                        kwargs[curTorrentProvider.getID() + '_enable_backlog'])
+                except:
+                    curTorrentProvider.enable_backlog = 0  # these exceptions are actually catching unselected checkboxes
 
-        if showObj is None:
-            errMsg = "Error", "Show not in show list"
-            if direct:
-                ui.notifications.error('Error', errMsg)
-                return json.dumps({'result': 'error'})
-            else:
-                return self._genericMessage("Error", errMsg)
+        for curNzbProvider in [curProvider for curProvider in sickbeard.providers.sortedProviderList() if
+                               curProvider.providerType == sickbeard.GenericProvider.NZB]:
 
-        segments = {}
-        if eps is not None:
+            if hasattr(curNzbProvider, 'api_key'):
+                try:
+                    curNzbProvider.api_key = str(kwargs[curNzbProvider.getID() + '_api_key']).strip()
+                except:
+                    curNzbProvider.api_key = None
 
-            sql_l = []
-            for curEp in eps.split('|'):
+            if hasattr(curNzbProvider, 'username'):
+                try:
+                    curNzbProvider.username = str(kwargs[curNzbProvider.getID() + '_username']).strip()
+                except:
+                    curNzbProvider.username = None
 
-                logger.log(u"Attempting to set status on episode " + curEp + " to " + status, logger.DEBUG)
+            if hasattr(curNzbProvider, 'search_mode'):
+                try:
+                    curNzbProvider.search_mode = str(kwargs[curNzbProvider.getID() + '_search_mode']).strip()
+                except:
+                    curNzbProvider.search_mode = 'eponly'
 
-                epInfo = curEp.split('x')
+            if hasattr(curNzbProvider, 'search_fallback'):
+                try:
+                    curNzbProvider.search_fallback = config.checkbox_to_value(
+                        kwargs[curNzbProvider.getID() + '_search_fallback'])
+                except:
+                    curNzbProvider.search_fallback = 0  # these exceptions are actually catching unselected checkboxes
 
-                epObj = showObj.getEpisode(int(epInfo[0]), int(epInfo[1]))
+            if hasattr(curNzbProvider, 'enable_daily'):
+                try:
+                    curNzbProvider.enable_daily = config.checkbox_to_value(
+                        kwargs[curNzbProvider.getID() + '_enable_daily'])
+                except:
+                    curNzbProvider.enable_daily = 0  # these exceptions are actually catching unselected checkboxes
 
-                if epObj is None:
-                    return self._genericMessage("Error", "Episode couldn't be retrieved")
+            if hasattr(curNzbProvider, 'enable_backlog'):
+                try:
+                    curNzbProvider.enable_backlog = config.checkbox_to_value(
+                        kwargs[curNzbProvider.getID() + '_enable_backlog'])
+                except:
+                    curNzbProvider.enable_backlog = 0  # these exceptions are actually catching unselected checkboxes
 
-                if int(status) in [WANTED, FAILED]:
-                    # figure out what episodes are wanted so we can backlog them
-                    if epObj.season in segments:
-                        segments[epObj.season].append(epObj)
-                    else:
-                        segments[epObj.season] = [epObj]
+        sickbeard.NEWZNAB_DATA = '!!!'.join([x.configStr() for x in sickbeard.newznabProviderList])
+        sickbeard.PROVIDER_ORDER = provider_list
 
-                with epObj.lock:
-                    # don't let them mess up UNAIRED episodes
-                    if epObj.status == UNAIRED:
-                        logger.log(u"Refusing to change status of " + curEp + " because it is UNAIRED", logger.ERROR)
-                        continue
+        sickbeard.save_config()
 
-                    if int(
-                            status) in Quality.DOWNLOADED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED + [
-                        IGNORED] and not ek.ek(os.path.isfile, epObj.location):
-                        logger.log(
-                            u"Refusing to change status of " + curEp + " to DOWNLOADED because it's not SNATCHED/DOWNLOADED",
-                            logger.ERROR)
-                        continue
+        if len(results) > 0:
+            for x in results:
+                logger.log(x, logger.ERROR)
+            ui.notifications.error('Error(s) Saving Configuration',
+                                   '<br />\n'.join(results))
+        else:
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
-                    if int(
-                            status) == FAILED and epObj.status not in Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.DOWNLOADED:
-                        logger.log(
-                            u"Refusing to change status of " + curEp + " to FAILED because it's not SNATCHED/DOWNLOADED",
-                            logger.ERROR)
-                        continue
+        return self.redirect("/config/providers/")
 
-                    epObj.status = int(status)
 
-                    # mass add to database
-                    sql_l.append(epObj.get_sql())
+@route('/config/notifications(/?.*)')
+class ConfigNotifications(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigNotifications, self).__init__(*args, **kwargs)
 
-            if len(sql_l) > 0:
-                myDB = db.DBConnection()
-                myDB.mass_action(sql_l)
+    def index(self):
+        t = PageTemplate(rh=self, file="config_notifications.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
 
-        if int(status) == WANTED:
-            msg = "Backlog was automatically started for the following seasons of <b>" + showObj.name + "</b>:<br />"
-            msg += '<ul>'
 
-            for season, segment in segments.items():
-                cur_backlog_queue_item = search_queue.BacklogQueueItem(showObj, segment)
-                sickbeard.searchQueueScheduler.action.add_item(cur_backlog_queue_item)  # @UndefinedVariable
+    def saveNotifications(self, use_kodi=None, kodi_always_on=None, kodi_notify_onsnatch=None,
+                          kodi_notify_ondownload=None,
+                          kodi_notify_onsubtitledownload=None, kodi_update_onlyfirst=None,
+                          kodi_update_library=None, kodi_update_full=None, kodi_host=None, kodi_username=None,
+                          kodi_password=None,
+                          use_plex=None, plex_notify_onsnatch=None, plex_notify_ondownload=None,
+                          plex_notify_onsubtitledownload=None, plex_update_library=None,
+                          plex_server_host=None, plex_host=None, plex_username=None, plex_password=None,
+                          use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None,
+                          growl_notify_onsubtitledownload=None, growl_host=None, growl_password=None,
+                          use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None,
+                          prowl_notify_onsubtitledownload=None, prowl_api=None, prowl_priority=0,
+                          use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None,
+                          twitter_notify_onsubtitledownload=None,
+                          use_boxcar=None, boxcar_notify_onsnatch=None, boxcar_notify_ondownload=None,
+                          boxcar_notify_onsubtitledownload=None, boxcar_username=None,
+                          use_boxcar2=None, boxcar2_notify_onsnatch=None, boxcar2_notify_ondownload=None,
+                          boxcar2_notify_onsubtitledownload=None, boxcar2_accesstoken=None,
+                          use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None,
+                          pushover_notify_onsubtitledownload=None, pushover_userkey=None, pushover_apikey=None,
+                          use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None,
+                          libnotify_notify_onsubtitledownload=None,
+                          use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None,
+                          use_nmjv2=None, nmjv2_host=None, nmjv2_dbloc=None, nmjv2_database=None,
+                          use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None,
+                          trakt_remove_watchlist=None, trakt_use_watchlist=None, trakt_method_add=None,
+                          trakt_start_paused=None, trakt_use_recommended=None, trakt_sync=None,
+                          trakt_default_indexer=None, trakt_remove_serieslist=None,
+                          use_synologynotifier=None, synologynotifier_notify_onsnatch=None,
+                          synologynotifier_notify_ondownload=None, synologynotifier_notify_onsubtitledownload=None,
+                          use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None,
+                          pytivo_notify_onsubtitledownload=None, pytivo_update_library=None,
+                          pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None,
+                          use_nma=None, nma_notify_onsnatch=None, nma_notify_ondownload=None,
+                          nma_notify_onsubtitledownload=None, nma_api=None, nma_priority=0,
+                          use_pushalot=None, pushalot_notify_onsnatch=None, pushalot_notify_ondownload=None,
+                          pushalot_notify_onsubtitledownload=None, pushalot_authorizationtoken=None,
+                          use_pushbullet=None, pushbullet_notify_onsnatch=None, pushbullet_notify_ondownload=None,
+                          pushbullet_notify_onsubtitledownload=None, pushbullet_api=None, pushbullet_device=None,
+                          pushbullet_device_list=None,
+                          use_email=None, email_notify_onsnatch=None, email_notify_ondownload=None,
+                          email_notify_onsubtitledownload=None, email_host=None, email_port=25, email_from=None,
+                          email_tls=None, email_user=None, email_password=None, email_list=None, email_show_list=None,
+                          email_show=None):
 
-                msg += "<li>Season " + str(season) + "</li>"
-                logger.log(u"Sending backlog for " + showObj.name + " season " + str(
-                    season) + " because some eps were set to wanted")
+        results = []
 
-            msg += "</ul>"
+        sickbeard.USE_KODI = config.checkbox_to_value(use_kodi)
+        sickbeard.KODI_ALWAYS_ON = config.checkbox_to_value(kodi_always_on)
+        sickbeard.KODI_NOTIFY_ONSNATCH = config.checkbox_to_value(kodi_notify_onsnatch)
+        sickbeard.KODI_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(kodi_notify_ondownload)
+        sickbeard.KODI_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(kodi_notify_onsubtitledownload)
+        sickbeard.KODI_UPDATE_LIBRARY = config.checkbox_to_value(kodi_update_library)
+        sickbeard.KODI_UPDATE_FULL = config.checkbox_to_value(kodi_update_full)
+        sickbeard.KODI_UPDATE_ONLYFIRST = config.checkbox_to_value(kodi_update_onlyfirst)
+        sickbeard.KODI_HOST = config.clean_hosts(kodi_host)
+        sickbeard.KODI_USERNAME = kodi_username
+        sickbeard.KODI_PASSWORD = kodi_password
 
-            if segments:
-                ui.notifications.message("Backlog started", msg)
+        sickbeard.USE_PLEX = config.checkbox_to_value(use_plex)
+        sickbeard.PLEX_NOTIFY_ONSNATCH = config.checkbox_to_value(plex_notify_onsnatch)
+        sickbeard.PLEX_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(plex_notify_ondownload)
+        sickbeard.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(plex_notify_onsubtitledownload)
+        sickbeard.PLEX_UPDATE_LIBRARY = config.checkbox_to_value(plex_update_library)
+        sickbeard.PLEX_HOST = config.clean_hosts(plex_host)
+        sickbeard.PLEX_SERVER_HOST = config.clean_host(plex_server_host)
+        sickbeard.PLEX_USERNAME = plex_username
+        sickbeard.PLEX_PASSWORD = plex_password
 
-        if int(status) == FAILED:
-            msg = "Retrying Search was automatically started for the following season of <b>" + showObj.name + "</b>:<br />"
-            msg += '<ul>'
+        sickbeard.USE_GROWL = config.checkbox_to_value(use_growl)
+        sickbeard.GROWL_NOTIFY_ONSNATCH = config.checkbox_to_value(growl_notify_onsnatch)
+        sickbeard.GROWL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(growl_notify_ondownload)
+        sickbeard.GROWL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(growl_notify_onsubtitledownload)
+        sickbeard.GROWL_HOST = config.clean_host(growl_host, default_port=23053)
+        sickbeard.GROWL_PASSWORD = growl_password
 
-            for season, segment in segments.items():
-                cur_failed_queue_item = search_queue.FailedQueueItem(showObj, [segment])
-                sickbeard.searchQueueScheduler.action.add_item(cur_failed_queue_item)  # @UndefinedVariable
+        sickbeard.USE_PROWL = config.checkbox_to_value(use_prowl)
+        sickbeard.PROWL_NOTIFY_ONSNATCH = config.checkbox_to_value(prowl_notify_onsnatch)
+        sickbeard.PROWL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(prowl_notify_ondownload)
+        sickbeard.PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(prowl_notify_onsubtitledownload)
+        sickbeard.PROWL_API = prowl_api
+        sickbeard.PROWL_PRIORITY = prowl_priority
 
-                msg += "<li>Season " + str(season) + "</li>"
-                logger.log(u"Retrying Search for " + showObj.name + " season " + str(
-                    season) + " because some eps were set to failed")
+        sickbeard.USE_TWITTER = config.checkbox_to_value(use_twitter)
+        sickbeard.TWITTER_NOTIFY_ONSNATCH = config.checkbox_to_value(twitter_notify_onsnatch)
+        sickbeard.TWITTER_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(twitter_notify_ondownload)
+        sickbeard.TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(twitter_notify_onsubtitledownload)
 
-            msg += "</ul>"
+        sickbeard.USE_BOXCAR = config.checkbox_to_value(use_boxcar)
+        sickbeard.BOXCAR_NOTIFY_ONSNATCH = config.checkbox_to_value(boxcar_notify_onsnatch)
+        sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(boxcar_notify_ondownload)
+        sickbeard.BOXCAR_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(boxcar_notify_onsubtitledownload)
+        sickbeard.BOXCAR_USERNAME = boxcar_username
 
-            if segments:
-                ui.notifications.message("Retry Search started", msg)
+        sickbeard.USE_BOXCAR2 = config.checkbox_to_value(use_boxcar2)
+        sickbeard.BOXCAR2_NOTIFY_ONSNATCH = config.checkbox_to_value(boxcar2_notify_onsnatch)
+        sickbeard.BOXCAR2_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(boxcar2_notify_ondownload)
+        sickbeard.BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(boxcar2_notify_onsubtitledownload)
+        sickbeard.BOXCAR2_ACCESSTOKEN = boxcar2_accesstoken
 
-        if direct:
-            return json.dumps({'result': 'success'})
-        else:
-            redirect("/home/displayShow?show=" + show)
+        sickbeard.USE_PUSHOVER = config.checkbox_to_value(use_pushover)
+        sickbeard.PUSHOVER_NOTIFY_ONSNATCH = config.checkbox_to_value(pushover_notify_onsnatch)
+        sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushover_notify_ondownload)
+        sickbeard.PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushover_notify_onsubtitledownload)
+        sickbeard.PUSHOVER_USERKEY = pushover_userkey
+        sickbeard.PUSHOVER_APIKEY = pushover_apikey
 
+        sickbeard.USE_LIBNOTIFY = config.checkbox_to_value(use_libnotify)
+        sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH = config.checkbox_to_value(libnotify_notify_onsnatch)
+        sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(libnotify_notify_ondownload)
+        sickbeard.LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(libnotify_notify_onsubtitledownload)
 
-    def testRename(self, show=None):
+        sickbeard.USE_NMJ = config.checkbox_to_value(use_nmj)
+        sickbeard.NMJ_HOST = config.clean_host(nmj_host)
+        sickbeard.NMJ_DATABASE = nmj_database
+        sickbeard.NMJ_MOUNT = nmj_mount
 
-        if show is None:
-            return self._genericMessage("Error", "You must specify a show")
+        sickbeard.USE_NMJv2 = config.checkbox_to_value(use_nmjv2)
+        sickbeard.NMJv2_HOST = config.clean_host(nmjv2_host)
+        sickbeard.NMJv2_DATABASE = nmjv2_database
+        sickbeard.NMJv2_DBLOC = nmjv2_dbloc
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+        sickbeard.USE_SYNOINDEX = config.checkbox_to_value(use_synoindex)
 
-        if showObj is None:
-            return self._genericMessage("Error", "Show not in show list")
+        sickbeard.USE_SYNOLOGYNOTIFIER = config.checkbox_to_value(use_synologynotifier)
+        sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH = config.checkbox_to_value(synologynotifier_notify_onsnatch)
+        sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(synologynotifier_notify_ondownload)
+        sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(
+            synologynotifier_notify_onsubtitledownload)
 
-        try:
-            show_loc = showObj.location  # @UnusedVariable
-        except exceptions.ShowDirNotFoundException:
-            return self._genericMessage("Error", "Can't rename episodes when the show dir is missing.")
+        sickbeard.USE_TRAKT = config.checkbox_to_value(use_trakt)
+        sickbeard.TRAKT_USERNAME = trakt_username
+        sickbeard.TRAKT_PASSWORD = trakt_password
+        sickbeard.TRAKT_API = trakt_api
+        sickbeard.TRAKT_REMOVE_WATCHLIST = config.checkbox_to_value(trakt_remove_watchlist)
+        sickbeard.TRAKT_REMOVE_SERIESLIST = config.checkbox_to_value(trakt_remove_serieslist)
+        sickbeard.TRAKT_USE_WATCHLIST = config.checkbox_to_value(trakt_use_watchlist)
+        sickbeard.TRAKT_METHOD_ADD = int(trakt_method_add)
+        sickbeard.TRAKT_START_PAUSED = config.checkbox_to_value(trakt_start_paused)
+        sickbeard.TRAKT_USE_RECOMMENDED = config.checkbox_to_value(trakt_use_recommended)
+        sickbeard.TRAKT_SYNC = config.checkbox_to_value(trakt_sync)
+        sickbeard.TRAKT_DEFAULT_INDEXER = int(trakt_default_indexer)
 
-        ep_obj_rename_list = []
+        if sickbeard.USE_TRAKT:
+            sickbeard.traktCheckerScheduler.silent = False
+        else:
+            sickbeard.traktCheckerScheduler.silent = True
 
-        ep_obj_list = showObj.getAllEpisodes(has_location=True)
+        sickbeard.USE_EMAIL = config.checkbox_to_value(use_email)
+        sickbeard.EMAIL_NOTIFY_ONSNATCH = config.checkbox_to_value(email_notify_onsnatch)
+        sickbeard.EMAIL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(email_notify_ondownload)
+        sickbeard.EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(email_notify_onsubtitledownload)
+        sickbeard.EMAIL_HOST = config.clean_host(email_host)
+        sickbeard.EMAIL_PORT = config.to_int(email_port, default=25)
+        sickbeard.EMAIL_FROM = email_from
+        sickbeard.EMAIL_TLS = config.checkbox_to_value(email_tls)
+        sickbeard.EMAIL_USER = email_user
+        sickbeard.EMAIL_PASSWORD = email_password
+        sickbeard.EMAIL_LIST = email_list
 
-        for cur_ep_obj in ep_obj_list:
-            # Only want to rename if we have a location
-            if cur_ep_obj.location:
-                if cur_ep_obj.relatedEps:
-                    # do we have one of multi-episodes in the rename list already
-                    have_already = False
-                    for cur_related_ep in cur_ep_obj.relatedEps + [cur_ep_obj]:
-                        if cur_related_ep in ep_obj_rename_list:
-                            have_already = True
-                            break
-                        if not have_already:
-                            ep_obj_rename_list.append(cur_ep_obj)
-                else:
-                    ep_obj_rename_list.append(cur_ep_obj)
+        sickbeard.USE_PYTIVO = config.checkbox_to_value(use_pytivo)
+        sickbeard.PYTIVO_NOTIFY_ONSNATCH = config.checkbox_to_value(pytivo_notify_onsnatch)
+        sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pytivo_notify_ondownload)
+        sickbeard.PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pytivo_notify_onsubtitledownload)
+        sickbeard.PYTIVO_UPDATE_LIBRARY = config.checkbox_to_value(pytivo_update_library)
+        sickbeard.PYTIVO_HOST = config.clean_host(pytivo_host)
+        sickbeard.PYTIVO_SHARE_NAME = pytivo_share_name
+        sickbeard.PYTIVO_TIVO_NAME = pytivo_tivo_name
 
-        if ep_obj_rename_list:
-            # present season DESC episode DESC on screen
-            ep_obj_rename_list.reverse()
+        sickbeard.USE_NMA = config.checkbox_to_value(use_nma)
+        sickbeard.NMA_NOTIFY_ONSNATCH = config.checkbox_to_value(nma_notify_onsnatch)
+        sickbeard.NMA_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(nma_notify_ondownload)
+        sickbeard.NMA_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(nma_notify_onsubtitledownload)
+        sickbeard.NMA_API = nma_api
+        sickbeard.NMA_PRIORITY = nma_priority
 
-        t = PageTemplate(headers=self.request.headers, file="testRename.tmpl")
-        t.submenu = [{'title': 'Edit', 'path': 'home/editShow?show=%d' % showObj.indexerid}]
-        t.ep_obj_list = ep_obj_rename_list
-        t.show = showObj
+        sickbeard.USE_PUSHALOT = config.checkbox_to_value(use_pushalot)
+        sickbeard.PUSHALOT_NOTIFY_ONSNATCH = config.checkbox_to_value(pushalot_notify_onsnatch)
+        sickbeard.PUSHALOT_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushalot_notify_ondownload)
+        sickbeard.PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushalot_notify_onsubtitledownload)
+        sickbeard.PUSHALOT_AUTHORIZATIONTOKEN = pushalot_authorizationtoken
 
-        return _munge(t)
+        sickbeard.USE_PUSHBULLET = config.checkbox_to_value(use_pushbullet)
+        sickbeard.PUSHBULLET_NOTIFY_ONSNATCH = config.checkbox_to_value(pushbullet_notify_onsnatch)
+        sickbeard.PUSHBULLET_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushbullet_notify_ondownload)
+        sickbeard.PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushbullet_notify_onsubtitledownload)
+        sickbeard.PUSHBULLET_API = pushbullet_api
+        sickbeard.PUSHBULLET_DEVICE = pushbullet_device_list
 
+        sickbeard.save_config()
 
-    def doRename(self, show=None, eps=None):
+        if len(results) > 0:
+            for x in results:
+                logger.log(x, logger.ERROR)
+            ui.notifications.error('Error(s) Saving Configuration',
+                                   '<br />\n'.join(results))
+        else:
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
-        if show is None or eps is None:
-            errMsg = "You must specify a show and at least one episode"
-            return self._genericMessage("Error", errMsg)
+        return self.redirect("/config/notifications/")
 
-        show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
 
-        if show_obj is None:
-            errMsg = "Error", "Show not in show list"
-            return self._genericMessage("Error", errMsg)
+@route('/config/subtitles(/?.*)')
+class ConfigSubtitles(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigSubtitles, self).__init__(*args, **kwargs)
+
+    def index(self):
+        t = PageTemplate(rh=self, file="config_subtitles.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
 
-        try:
-            show_loc = show_obj.location  # @UnusedVariable
-        except exceptions.ShowDirNotFoundException:
-            return self._genericMessage("Error", "Can't rename episodes when the show dir is missing.")
 
-        if eps is None:
-            redirect("/home/displayShow?show=" + show)
+    def saveSubtitles(self, use_subtitles=None, subtitles_plugins=None, subtitles_languages=None, subtitles_dir=None,
+                      service_order=None, subtitles_history=None, subtitles_finder_frequency=None,
+                      subtitles_multi=None):
 
-        myDB = db.DBConnection()
-        for curEp in eps.split('|'):
+        results = []
 
-            epInfo = curEp.split('x')
+        if subtitles_finder_frequency == '' or subtitles_finder_frequency is None:
+            subtitles_finder_frequency = 1
 
-            # this is probably the worst possible way to deal with double eps but I've kinda painted myself into a corner here with this stupid database
-            ep_result = myDB.select(
-                "SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND 5=5",
-                [show, epInfo[0], epInfo[1]])
-            if not ep_result:
-                logger.log(u"Unable to find an episode for " + curEp + ", skipping", logger.WARNING)
-                continue
-            related_eps_result = myDB.select("SELECT * FROM tv_episodes WHERE location = ? AND episode != ?",
-                                             [ep_result[0]["location"], epInfo[1]])
+        if use_subtitles == "on" and not sickbeard.subtitlesFinderScheduler.isAlive():
+            sickbeard.subtitlesFinderScheduler.silent = False
+            try:
+                sickbeard.subtitlesFinderScheduler.start()
+            except:
+                pass
+        elif not use_subtitles == "on":
+            sickbeard.subtitlesFinderScheduler.stop.set()
+            sickbeard.subtitlesFinderScheduler.silent = True
+            try:
+                sickbeard.subtitlesFinderScheduler.join(5)
+            except:
+                pass
 
-            root_ep_obj = show_obj.getEpisode(int(epInfo[0]), int(epInfo[1]))
-            root_ep_obj.relatedEps = []
+        sickbeard.USE_SUBTITLES = config.checkbox_to_value(use_subtitles)
+        sickbeard.SUBTITLES_LANGUAGES = [lang.alpha2 for lang in subtitles.isValidLanguage(
+            subtitles_languages.replace(' ', '').split(','))] if subtitles_languages != '' else ''
+        sickbeard.SUBTITLES_DIR = subtitles_dir
+        sickbeard.SUBTITLES_HISTORY = config.checkbox_to_value(subtitles_history)
+        sickbeard.SUBTITLES_FINDER_FREQUENCY = config.to_int(subtitles_finder_frequency, default=1)
+        sickbeard.SUBTITLES_MULTI = config.checkbox_to_value(subtitles_multi)
 
-            for cur_related_ep in related_eps_result:
-                related_ep_obj = show_obj.getEpisode(int(cur_related_ep["season"]), int(cur_related_ep["episode"]))
-                if related_ep_obj not in root_ep_obj.relatedEps:
-                    root_ep_obj.relatedEps.append(related_ep_obj)
+        # Subtitles services
+        services_str_list = service_order.split()
+        subtitles_services_list = []
+        subtitles_services_enabled = []
+        for curServiceStr in services_str_list:
+            curService, curEnabled = curServiceStr.split(':')
+            subtitles_services_list.append(curService)
+            subtitles_services_enabled.append(int(curEnabled))
 
-            root_ep_obj.rename()
+        sickbeard.SUBTITLES_SERVICES_LIST = subtitles_services_list
+        sickbeard.SUBTITLES_SERVICES_ENABLED = subtitles_services_enabled
 
-        redirect("/home/displayShow?show=" + show)
+        sickbeard.save_config()
 
-    def searchEpisode(self, show=None, season=None, episode=None):
+        if len(results) > 0:
+            for x in results:
+                logger.log(x, logger.ERROR)
+            ui.notifications.error('Error(s) Saving Configuration',
+                                   '<br />\n'.join(results))
+        else:
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
-        # 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'})
+        return self.redirect("/config/subtitles/")
 
-        # make a queue item for it and put it on the queue
-        ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj.show, ep_obj)
 
-        sickbeard.searchQueueScheduler.action.add_item(ep_queue_item)  # @UndefinedVariable
+@route('/config/anime(/?.*)')
+class ConfigAnime(Config):
+    def __init__(self, *args, **kwargs):
+        super(ConfigAnime, self).__init__(*args, **kwargs)
 
-        if not ep_queue_item.started and ep_queue_item.success is None:
-            return json.dumps(
-                {'result': 'success'})  # I Actually want to call it queued, because the search hasnt been started yet!
-        if ep_queue_item.started and ep_queue_item.success is None:
-            return json.dumps({'result': 'success'})
-        else:
-            return json.dumps({'result': 'failure'})
+    def index(self):
 
-    ### Returns the current ep_queue_item status for the current viewed show.
-    # Possible status: Downloaded, Snatched, etc...
-    # Returns {'show': 279530, 'episodes' : ['episode' : 6, 'season' : 1, 'searchstatus' : 'queued', 'status' : 'running', 'quality': '4013']
-    def getManualSearchStatus(self, show=None, season=None):
+        t = PageTemplate(rh=self, file="config_anime.tmpl")
+        t.submenu = self.ConfigMenu()
+        return t.respond()
 
-        episodes = []
-        currentManualSearchThreadsQueued = []
-        currentManualSearchThreadActive = []
-        finishedManualSearchThreadItems = []
 
-        # Queued Searches
-        currentManualSearchThreadsQueued = sickbeard.searchQueueScheduler.action.get_all_ep_from_queue(show)
+    def saveAnime(self, use_anidb=None, anidb_username=None, anidb_password=None, anidb_use_mylist=None,
+                  split_home=None):
 
-        # Running Searches
-        if (sickbeard.searchQueueScheduler.action.is_manualsearch_in_progress()):
-            currentManualSearchThreadActive = sickbeard.searchQueueScheduler.action.currentItem
+        results = []
 
-        # Finished Searches
-        finishedManualSearchThreadItems = sickbeard.search_queue.MANUAL_SEARCH_HISTORY
-
-        if currentManualSearchThreadsQueued:
-            for searchThread in currentManualSearchThreadsQueued:
-                searchstatus = 'queued'
-                if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem):
-                    episodes.append({'episode': searchThread.segment.episode,
-                                     'episodeindexid': searchThread.segment.indexerid,
-                                     'season': searchThread.segment.season,
-                                     'searchstatus': searchstatus,
-                                     'status': statusStrings[searchThread.segment.status],
-                                     'quality': self.getQualityClass(searchThread.segment)})
-                else:
-                    for epObj in searchThread.segment:
-                        episodes.append({'episode': epObj.episode,
-                                         'episodeindexid': epObj.indexerid,
-                                         'season': epObj.season,
-                                         'searchstatus': searchstatus,
-                                         'status': statusStrings[epObj.status],
-                                         'quality': self.getQualityClass(epObj)})
-
-        if currentManualSearchThreadActive:
-            searchThread = currentManualSearchThreadActive
-            searchstatus = 'searching'
-            if searchThread.success:
-                searchstatus = 'finished'
-            else:
-                searchstatus = 'searching'
-            episodes.append({'episode': searchThread.segment.episode,
-                             'episodeindexid': searchThread.segment.indexerid,
-                             'season': searchThread.segment.season,
-                             'searchstatus': searchstatus,
-                             'status': statusStrings[searchThread.segment.status],
-                             'quality': self.getQualityClass(searchThread.segment)})
-
-        if finishedManualSearchThreadItems:
-            for searchThread in finishedManualSearchThreadItems:
-                if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem):
-                    if str(searchThread.show.indexerid) == show and not [x for x in episodes if x[
-                        'episodeindexid'] == searchThread.segment.indexerid]:
-                        searchstatus = 'finished'
-                        episodes.append({'episode': searchThread.segment.episode,
-                                         'episodeindexid': searchThread.segment.indexerid,
-                                         'season': searchThread.segment.season,
-                                         'searchstatus': searchstatus,
-                                         'status': statusStrings[searchThread.segment.status],
-                                         'quality': self.getQualityClass(searchThread.segment)})
-                else:
-                    ### These are only Failed Downloads/Retry SearchThreadItems.. lets loop through the segement/episodes
-                    if str(searchThread.show.indexerid) == show:
-                        for epObj in searchThread.segment:
-                            if not [x for x in episodes if x['episodeindexid'] == epObj.indexerid]:
-                                searchstatus = 'finished'
-                                episodes.append({'episode': epObj.episode,
-                                                 'episodeindexid': epObj.indexerid,
-                                                 'season': epObj.season,
-                                                 'searchstatus': searchstatus,
-                                                 'status': statusStrings[epObj.status],
-                                                 'quality': self.getQualityClass(epObj)})
+        sickbeard.USE_ANIDB = config.checkbox_to_value(use_anidb)
+        sickbeard.ANIDB_USERNAME = anidb_username
+        sickbeard.ANIDB_PASSWORD = anidb_password
+        sickbeard.ANIDB_USE_MYLIST = config.checkbox_to_value(anidb_use_mylist)
+        sickbeard.ANIME_SPLIT_HOME = config.checkbox_to_value(split_home)
 
-        return json.dumps({'show': show, 'episodes': episodes})
+        sickbeard.save_config()
 
-    def getQualityClass(self, ep_obj):
-        # return the correct json value
+        if len(results) > 0:
+            for x in results:
+                logger.log(x, logger.ERROR)
+            ui.notifications.error('Error(s) Saving Configuration',
+                                   '<br />\n'.join(results))
+        else:
+            ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
 
-        # Find the quality class for the episode
-        quality_class = Quality.qualityStrings[Quality.UNKNOWN]
-        ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status)
-        for x in (SD, HD720p, HD1080p):
-            if ep_quality in Quality.splitQuality(x)[0]:
-                quality_class = qualityPresetStrings[x]
-                break
+        return self.redirect("/config/anime/")
 
-        return quality_class
 
-    def searchEpisodeSubtitles(self, show=None, season=None, episode=None):
-        # 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'})
+@route('/errorlogs(/?.*)')
+class ErrorLogs(WebRoot):
+    def __init__(self, *args, **kwargs):
+        super(ErrorLogs, self).__init__(*args, **kwargs)
 
-        # try do download subtitles for that episode
-        previous_subtitles = set(subliminal.language.Language(x) for x in ep_obj.subtitles)
-        try:
-            ep_obj.subtitles = set(x.language for x in ep_obj.downloadSubtitles().values()[0])
-        except:
-            return json.dumps({'result': 'failure'})
+    def ErrorLogsMenu(self):
+        menu = [
+            {'title': 'Clear Errors', 'path': 'errorlogs/clearerrors/'},
+            # { 'title': 'View Log',  'path': 'errorlogs/viewlog'  },
+        ]
 
-        # return the correct json value
-        if previous_subtitles != ep_obj.subtitles:
-            status = 'New subtitles downloaded: %s' % ' '.join([
-                "<img src='" + sickbeard.WEB_ROOT + "/images/flags/" + x.alpha2 +
-                ".png' alt='" + x.name + "'/>" for x in
-                sorted(list(ep_obj.subtitles.difference(previous_subtitles)))])
-        else:
-            status = 'No subtitles downloaded'
-        ui.notifications.message('Subtitles Search', status)
-        return json.dumps({'result': status, 'subtitles': ','.join(sorted([x.alpha2 for x in
-                                                                           ep_obj.subtitles.union(
-                                                                               previous_subtitles)]))})
+        return menu
 
-    def setSceneNumbering(self, show, indexer, forSeason=None, forEpisode=None, forAbsolute=None, sceneSeason=None,
-                          sceneEpisode=None, sceneAbsolute=None):
+    def index(self):
 
-        # sanitize:
-        if forSeason in ['null', '']: forSeason = None
-        if forEpisode in ['null', '']: forEpisode = None
-        if forAbsolute in ['null', '']: forAbsolute = None
-        if sceneSeason in ['null', '']: sceneSeason = None
-        if sceneEpisode in ['null', '']: sceneEpisode = None
-        if sceneAbsolute in ['null', '']: sceneAbsolute = None
+        t = PageTemplate(rh=self, file="errorlogs.tmpl")
+        t.submenu = self.ErrorLogsMenu()
 
-        showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
+        return t.respond()
 
-        if showObj.is_anime:
-            result = {
-                'success': True,
-                'forAbsolute': forAbsolute,
-            }
-        else:
-            result = {
-                'success': True,
-                'forSeason': forSeason,
-                'forEpisode': forEpisode,
-            }
 
-        # retrieve the episode object and fail if we can't get one
-        if showObj.is_anime:
-            ep_obj = _getEpisode(show, absolute=forAbsolute)
-        else:
-            ep_obj = _getEpisode(show, forSeason, forEpisode)
+    def clearerrors(self):
+        classes.ErrorViewer.clear()
+        return self.redirect("/errorlogs/")
 
-        if isinstance(ep_obj, str):
-            result['success'] = False
-            result['errorMessage'] = ep_obj
-        elif showObj.is_anime:
-            logger.log(u"setAbsoluteSceneNumbering for %s from %s to %s" %
-                       (show, forAbsolute, sceneAbsolute), logger.DEBUG)
 
-            show = int(show)
-            indexer = int(indexer)
-            forAbsolute = int(forAbsolute)
-            if sceneAbsolute is not None: sceneAbsolute = int(sceneAbsolute)
+    def viewlog(self, minLevel=logger.INFO, maxLines=500):
 
-            set_scene_numbering(show, indexer, absolute_number=forAbsolute, sceneAbsolute=sceneAbsolute)
-        else:
-            logger.log(u"setEpisodeSceneNumbering for %s from %sx%s to %sx%s" %
-                       (show, forSeason, forEpisode, sceneSeason, sceneEpisode), logger.DEBUG)
+        t = PageTemplate(rh=self, file="viewlogs.tmpl")
+        t.submenu = self.ErrorLogsMenu()
 
-            show = int(show)
-            indexer = int(indexer)
-            forSeason = int(forSeason)
-            forEpisode = int(forEpisode)
-            if sceneSeason is not None: sceneSeason = int(sceneSeason)
-            if sceneEpisode is not None: sceneEpisode = int(sceneEpisode)
+        minLevel = int(minLevel)
 
-            set_scene_numbering(show, indexer, season=forSeason, episode=forEpisode, sceneSeason=sceneSeason,
-                                sceneEpisode=sceneEpisode)
+        data = []
+        if os.path.isfile(logger.logFile):
+            with ek.ek(open, logger.logFile) as f:
+                data = f.readlines()
 
-        if showObj.is_anime:
-            sn = get_scene_absolute_numbering(show, indexer, forAbsolute)
-            if sn:
-                result['sceneAbsolute'] = sn
-            else:
-                result['sceneAbsolute'] = None
-        else:
-            sn = get_scene_numbering(show, indexer, forSeason, forEpisode)
-            if sn:
-                (result['sceneSeason'], result['sceneEpisode']) = sn
-            else:
-                (result['sceneSeason'], result['sceneEpisode']) = (None, None)
+        regex = "^(\d\d\d\d)\-(\d\d)\-(\d\d)\s*(\d\d)\:(\d\d):(\d\d)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$"
 
-        return json.dumps(result)
+        finalData = []
 
+        numLines = 0
+        lastLine = False
+        numToShow = min(maxLines, len(data))
 
-    def retryEpisode(self, show, season, episode):
+        for x in reversed(data):
 
-        # 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'})
+            x = ek.ss(x)
+            match = re.match(regex, x)
 
-        # make a queue item for it and put it on the queue
-        ep_queue_item = search_queue.FailedQueueItem(ep_obj.show, [ep_obj])
-        sickbeard.searchQueueScheduler.action.add_item(ep_queue_item)  # @UndefinedVariable
+            if match:
+                level = match.group(7)
+                if level not in logger.reverseNames:
+                    lastLine = False
+                    continue
 
-        if not ep_queue_item.started and ep_queue_item.success is None:
-            return json.dumps(
-                {'result': 'success'})  # I Actually want to call it queued, because the search hasnt been started yet!
-        if ep_queue_item.started and ep_queue_item.success is None:
-            return json.dumps({'result': 'success'})
-        else:
-            return json.dumps({'result': 'failure'})
+                if logger.reverseNames[level] >= minLevel:
+                    lastLine = True
+                    finalData.append(x)
+                else:
+                    lastLine = False
+                    continue
 
+            elif lastLine:
+                finalData.append("AA" + x)
 
-class UI(MainHandler):
-    def add_message(self):
-        ui.notifications.message('Test 1', 'This is test number 1')
-        ui.notifications.error('Test 2', 'This is test number 2')
+            numLines += 1
 
-        return "ok"
+            if numLines >= numToShow:
+                break
 
-    def get_messages(self):
-        messages = {}
-        cur_notification_num = 1
-        for cur_notification in ui.notifications.get_notifications(self.request.remote_ip):
-            messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title,
-                                                                     'message': cur_notification.message,
-                                                                     'type': cur_notification.type}
-            cur_notification_num += 1
+        result = "".join(finalData)
 
-        return json.dumps(messages)
+        t.logLines = result
+        t.minLevel = minLevel
+
+        return t.respond()
\ No newline at end of file
diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py
index f0c440c630f28c36e76125a59875010c94ad5d5b..8d154dcfc8cf9b2d32e36a9a21813310622e704e 100644
--- a/sickbeard/webserveInit.py
+++ b/sickbeard/webserveInit.py
@@ -1,40 +1,16 @@
 import os
-import socket
-import time
 import threading
 import sys
 import sickbeard
-import webserve
-import webapi
 
+from sickbeard.webserve import LoginHandler, LogoutHandler, KeyHandler
+from sickbeard.webapi import ApiHandler
 from sickbeard import logger
-from sickbeard.helpers import create_https_certificates
-from tornado.web import Application, StaticFileHandler, RedirectHandler, HTTPError
+from sickbeard.helpers import create_https_certificates, generateApiKey
+from tornado.web import Application, StaticFileHandler, HTTPError, RedirectHandler
 from tornado.httpserver import HTTPServer
 from tornado.ioloop import IOLoop
-
-
-class MultiStaticFileHandler(StaticFileHandler):
-    def initialize(self, paths, default_filename=None):
-        self.paths = paths
-        self.default_filename = default_filename
-
-    def get(self, path, include_body=True):
-        for p in self.paths:
-            try:
-                # Initialize the Static file with a path
-                super(MultiStaticFileHandler, self).initialize(p)
-                # Try to get the file
-                return super(MultiStaticFileHandler, self).get(path)
-            except HTTPError as exc:
-                # File not found, carry on
-                if exc.status_code == 404:
-                    continue
-                raise
-
-        # Oops file not found anywhere!
-        raise HTTPError(404)
-
+from tornado.routes import route
 
 class SRWebServer(threading.Thread):
     def __init__(self, options={}, io_loop=None):
@@ -62,8 +38,13 @@ class SRWebServer(threading.Thread):
             self.video_root = None
 
         # web root
-        self.options['web_root'] = ('/' + self.options['web_root'].lstrip('/')) if self.options[
-            'web_root'] else ''
+        if self.options['web_root']:
+            sickbeard.WEB_ROOT = self.options['web_root'] = ('/' + self.options['web_root'].lstrip('/').strip('/'))
+
+        # api root
+        if not sickbeard.API_KEY:
+            sickbeard.API_KEY = generateApiKey()
+        self.options['api_root'] = r'%s/api/%s' % (sickbeard.WEB_ROOT, sickbeard.API_KEY)
 
         # tornado setup
         self.enable_https = self.options['enable_https']
@@ -73,7 +54,7 @@ class SRWebServer(threading.Thread):
         if self.enable_https:
             # If either the HTTPS certificate or key do not exist, make some self-signed ones.
             if not (self.https_cert and os.path.exists(self.https_cert)) or not (
-                self.https_key and os.path.exists(self.https_key)):
+                        self.https_key and os.path.exists(self.https_key)):
                 if not create_https_certificates(self.https_cert, self.https_key):
                     logger.log(u"Unable to create CERT/KEY files, disabling HTTPS")
                     sickbeard.ENABLE_HTTPS = False
@@ -90,35 +71,56 @@ class SRWebServer(threading.Thread):
                                  autoreload=False,
                                  gzip=True,
                                  xheaders=sickbeard.HANDLE_REVERSE_PROXY,
-                                 cookie_secret='61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo='
+                                 cookie_secret='61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=',
+                                 login_url='/login/',
         )
 
-        # Main Handler
-        self.app.add_handlers(".*$", [
-            (r'%s/api/(.*)(/?)' % self.options['web_root'], webapi.Api),
-            (r'%s/(.*)(/?)' % self.options['web_root'], webserve.MainHandler),
-            (r'(.*)', webserve.MainHandler)
-        ])
+        # Main Handlers
+        self.app.add_handlers('.*$', [
+            # webapi handler
+            (r'%s(/?.*)' % self.options['api_root'], ApiHandler),
+
+            # webapi key retrieval
+            (r'%s/getkey(/?.*)' % self.options['web_root'], KeyHandler),
+
+            # webapi builder redirect
+            (r'%s/api/builder' % self.options['web_root'], RedirectHandler, {"url": self.options['web_root'] + '/apibuilder/'}),
 
-        # Static Path Handler
+            # webui login/logout handlers
+            (r'%s/login(/?.*)' % self.options['web_root'], LoginHandler),
+            (r'%s/logout(/?.*)' % self.options['web_root'], LogoutHandler),
+
+            # webui redirect
+            (r'/', RedirectHandler, {"url": self.options['web_root'] + '/home/'}),
+
+            # webui handlers
+        ] + route.get_routes(self.options['web_root']))
+
+        # Static File Handlers
         self.app.add_handlers(".*$", [
-            (r'%s/(favicon\.ico)' % self.options['web_root'], MultiStaticFileHandler,
-             {'paths': [os.path.join(self.options['data_root'], 'images/ico/favicon.ico')]}),
-            (r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'images'), MultiStaticFileHandler,
-             {'paths': [os.path.join(self.options['data_root'], 'images'),
-                        os.path.join(sickbeard.CACHE_DIR, 'images')]}),
-            (r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'css'), MultiStaticFileHandler,
-             {'paths': [os.path.join(self.options['data_root'], 'css')]}),
-            (r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'js'), MultiStaticFileHandler,
-             {'paths': [os.path.join(self.options['data_root'], 'js')]}),
-        ])
-
-        # Static Videos Path
-        if self.video_root:
-            self.app.add_handlers(".*$", [
-                (r'%s/%s/(.*)' % (self.options['web_root'], 'videos'), MultiStaticFileHandler,
-                 {'paths': [self.video_root]}),
-            ])
+            # favicon
+            (r'%s/(favicon\.ico)' % self.options['web_root'], StaticFileHandler,
+             {"path": os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}),
+
+            # images
+            (r'%s/images/(.*)' % self.options['web_root'], StaticFileHandler,
+             {"path": os.path.join(self.options['data_root'], 'images')}),
+
+            # cached images
+            (r'%s/cache/images/(.*)' % self.options['web_root'], StaticFileHandler,
+             {"path": os.path.join(sickbeard.CACHE_DIR, 'images')}),
+
+            # css
+            (r'%s/css/(.*)' % self.options['web_root'], StaticFileHandler,
+             {"path": os.path.join(self.options['data_root'], 'css')}),
+
+            # javascript
+            (r'%s/js/(.*)' % self.options['web_root'], StaticFileHandler,
+             {"path": os.path.join(self.options['data_root'], 'js')}),
+
+            # videos
+        ] + [(r'%s/videos/(.*)' % self.options['web_root'], StaticFileHandler,
+              {"path": self.video_root})])
 
     def run(self):
         if self.enable_https:
diff --git a/tests/db_tests.py b/tests/db_tests.py
index fe988f62ce5a4bf8c33846774139e634cee57818..4802ad97b28caf035d30a7f270cda9c234a1257e 100644
--- a/tests/db_tests.py
+++ b/tests/db_tests.py
@@ -19,10 +19,9 @@
 
 import unittest
 import test_lib as test
-
+import threading
 
 class DBBasicTests(test.SickbeardTestDBCase):
-
     def setUp(self):
         super(DBBasicTests, self).setUp()
         self.db = test.db.DBConnection()
@@ -30,6 +29,18 @@ class DBBasicTests(test.SickbeardTestDBCase):
     def test_select(self):
         self.db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000])
 
+class DBMultiTests(test.SickbeardTestDBCase):
+    def setUp(self):
+        super(DBMultiTests, self).setUp()
+        self.db = test.db.DBConnection()
+
+    def select(self):
+        self.db.select("SELECT * FROM tv_episodes WHERE showid = ? AND location != ''", [0000])
+
+    def test_threaded(self):
+        for i in xrange(4):
+            t = threading.Thread(target=self.select)
+            t.start()
 
 if __name__ == '__main__':
     print "=================="
@@ -38,3 +49,6 @@ if __name__ == '__main__':
     print "######################################################################"
     suite = unittest.TestLoader().loadTestsFromTestCase(DBBasicTests)
     unittest.TextTestRunner(verbosity=2).run(suite)
+
+    #suite = unittest.TestLoader().loadTestsFromTestCase(DBMultiTests)
+    #unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/encoding_tests.py b/tests/encoding_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..40089b27074b695c8280eb870df8de90f33d5077
--- /dev/null
+++ b/tests/encoding_tests.py
@@ -0,0 +1,44 @@
+# coding=utf-8
+import locale
+import unittest
+import sys, os.path
+
+sys.path.append(os.path.abspath('..'))
+sys.path.append(os.path.abspath('../lib'))
+
+import sickbeard
+from sickbeard import encodingKludge as ek
+from sickbeard.exceptions import ex
+from sickbeard.helpers import sanitizeFileName
+
+class EncodingTests(unittest.TestCase):
+    def test_encoding(self):
+        rootDir = 'C:\\Temp\\TV'
+        strings = [u'Les Enfants De La T\xe9l\xe9', u'RT� One']
+
+        sickbeard.SYS_ENCODING = None
+
+        try:
+            locale.setlocale(locale.LC_ALL, "")
+            sickbeard.SYS_ENCODING = locale.getpreferredencoding()
+        except (locale.Error, IOError):
+            pass
+
+        # For OSes that are poorly configured I'll just randomly force UTF-8
+        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
+            sickbeard.SYS_ENCODING = 'UTF-8'
+
+        for s in strings:
+            try:
+                show_dir = ek.ek(os.path.join, rootDir, sanitizeFileName(s))
+                self.assertTrue(isinstance(show_dir, unicode))
+            except Exception, e:
+                ex(e)
+
+if __name__ == "__main__":
+    print "=================="
+    print "STARTING - ENCODING TESTS"
+    print "=================="
+    print "######################################################################"
+    suite = unittest.TestLoader().loadTestsFromTestCase(EncodingTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
\ No newline at end of file
diff --git a/tests/test_lib.py b/tests/test_lib.py
index 201d218226b7c19594bdbd91359bfcb0601beb67..f87d4dc368c17363483f4a158f69ff6e0342ecfe 100644
--- a/tests/test_lib.py
+++ b/tests/test_lib.py
@@ -32,10 +32,11 @@ sys.path.append(os.path.abspath('../lib'))
 import sickbeard
 import shutil
 
-from sickbeard import encodingKludge as ek, providers, tvcache
+from sickbeard import providers, tvcache
 from sickbeard import db
 from sickbeard.databases import mainDB
 from sickbeard.databases import cache_db, failed_db
+from sickbeard.tv import TVEpisode
 
 #=================
 # test globals
@@ -53,9 +54,8 @@ FILEDIR = os.path.join(TESTDIR, SHOWNAME)
 FILEPATH = os.path.join(FILEDIR, FILENAME)
 SHOWDIR = os.path.join(TESTDIR, SHOWNAME + " final")
 
-#sickbeard.logger.sb_log_instance = sickbeard.logger.SBRotatingLogHandler(os.path.join(TESTDIR, 'sickbeard.log'), sickbeard.logger.NUM_LOGS, sickbeard.logger.LOG_SIZE)
-sickbeard.logger.SBRotatingLogHandler.log_file = os.path.join(os.path.join(TESTDIR, 'Logs'), 'test_sickbeard.log')
-
+sickbeard.logger.logFile = os.path.join(os.path.join(TESTDIR, 'Logs'), 'test_sickbeard.log')
+sickbeard.logger.initLogging()
 
 #=================
 # prepare env functions
@@ -92,7 +92,7 @@ sickbeard.PROG_DIR = os.path.abspath('..')
 sickbeard.DATA_DIR = sickbeard.PROG_DIR
 sickbeard.LOG_DIR = os.path.join(TESTDIR, 'Logs')
 createTestLogFolder()
-sickbeard.logger.sb_log_instance.initLogging(False)
+sickbeard.logger.initLogging(False)
 
 sickbeard.CACHE_DIR = os.path.join(TESTDIR, 'cache')
 createTestCacheFolder()
@@ -112,8 +112,7 @@ mainDB.sickbeard.save_config = _dummy_saveConfig
 def _fake_specifyEP(self, season, episode):
     pass
 
-sickbeard.tv.TVEpisode.specifyEpisode = _fake_specifyEP
-
+TVEpisode.specifyEpisode = _fake_specifyEP
 
 #=================
 # test classes
diff --git a/tornado/routes.py b/tornado/routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7042aa0e596f3ce9f22934881013da6e89d2250
--- /dev/null
+++ b/tornado/routes.py
@@ -0,0 +1,27 @@
+import inspect
+import os
+import tornado.web
+
+route_list = []
+
+class route(object):
+    _routes = []
+
+    def __init__(self, uri, name=None):
+        self._uri = uri
+        self.name = name
+
+    def __call__(self, _handler):
+        """gets called when we class decorate"""
+        name = self.name and self.name or _handler.__name__
+        self._routes.append((self._uri, _handler, name))
+        return _handler
+
+    @classmethod
+    def get_routes(self, webroot=''):
+        self._routes.reverse()
+        routes = [tornado.web.url(webroot + _uri, _handler, name=name) for _uri, _handler, name, in self._routes]
+        return routes
+
+def route_redirect(from_, to, name=None):
+    route._routes.append(tornado.web.url(from_, tornado.web.RedirectHandler, dict(url=to), name=name))
\ No newline at end of file