diff --git a/SickBeard.py b/SickBeard.py
index 7b67dc411c660063beee651d1765dad628fc989b..e40ee3de2399f51e7f8a7e75dd9c7230e60c4727 100755
--- a/SickBeard.py
+++ b/SickBeard.py
@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-# Check needed software  dependencies to nudge users to fix their setup
+# Check needed software dependencies to nudge users to fix their setup
 import sys
 if sys.version_info < (2, 5):
     print "Sorry, requires Python 2.5, 2.6 or 2.7."
@@ -52,6 +52,7 @@ from sickbeard import db
 from sickbeard.tv import TVShow
 from sickbeard import logger
 from sickbeard.version import SICKBEARD_VERSION
+from sickbeard.databases.mainDB import MAX_DB_VERSION
 
 from sickbeard.webserveInit import initWebServer
 
@@ -261,6 +262,11 @@ def main():
 
     sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
 
+    if db.DBConnection().checkDBVersion() > MAX_DB_VERSION:
+        print 'Your database version has been incremented past what this version of Sick Beard supports.'
+        print 'Have you used other forks of Sick Beard with this same database file?'
+        sys.exit(1)
+
     # Initialize the config and our threads
     sickbeard.initialize(consoleLogging=consoleLogging)
 
diff --git a/data/css/style.css b/data/css/style.css
index 967959eade1fe70f7ff162f08f4030b0efc7d20d..0187128b0237d8a88dcb214e82341b3ba365b77e 100644
--- a/data/css/style.css
+++ b/data/css/style.css
@@ -531,7 +531,9 @@ displayShow.tmpl + manage_backlogOverview.tmpl
 .wanted {
   background-color: #ffb0b0;
 }
-
+.snatched {
+  background-color: #ebc1ea;
+}
 /* =======================================================================
 manage_backlogOverview.tmpl
 ========================================================================== */
@@ -1056,12 +1058,20 @@ span.quality {
 }
 span.Custom {
   background: none repeat scroll 0 0 #449;
-  /* blue */
+  /* purplish blue */
+}
+span.HD {
+  background: none repeat scroll 0 0 #008fbb;
+  /* greenish blue */
 }
-span.HD,span.WEB-DL,span.BluRay {
+span.HD720p {
   background: none repeat scroll 0 0 #494;
   /* green */
 }
+span.HD1080p {
+  background: none repeat scroll 0 0 #499;
+  /* blue */
+}
 span.SD {
   background: none repeat scroll 0 0 #944;
   /* red */
@@ -1070,6 +1080,10 @@ span.Any {
   background: none repeat scroll 0 0 #444;
   /* black */
 }
+span.RawHD {
+  background: none repeat scroll 0 0 #999944;
+  /* dark orange */
+}
 /* unused boolean tags */
 span.false {
   color: #933;
diff --git a/data/interfaces/default/apiBuilder.tmpl b/data/interfaces/default/apiBuilder.tmpl
index c454e7bccbb843690f67220cbfcf1b965465a4f4..1234074cb6a26fa88ccae02debd4ecb9638c474c 100644
--- a/data/interfaces/default/apiBuilder.tmpl
+++ b/data/interfaces/default/apiBuilder.tmpl
@@ -190,14 +190,19 @@ addList("show.setquality", "$curShow.name", "&tvdbid=$curShow.tvdbid", "quality"
 //build out generic quality options
 addOptGroup("quality", "Quality Templates");
 addOption("quality", "SD", "&initial=sdtv|sddvd");
-addOption("quality", "HD", "&initial=hdtv|hdwebdl|hdbluray");
-addOption("quality", "ANY", "&initial=sdtv|sddvd|hdtv|hdwebdl|hdbluray|unknown");
+addOption("quality", "HD", "&initial=hdtv|fullhdtv|hdwebdl|fullhdwebdl|hdbluray|fullhdbluray");
+addOption("quality", "HD720p", "&initial=hdtv|hdwebdl|hdbluray");
+addOption("quality", "HD1080p", "&initial=fullhdtv|fullhdwebdl|fullhdbluray");
+addOption("quality", "ANY", "&initial=sdtv|sddvd|hdtv|fullhdtv|hdwebdl|fullhdwebdl|hdbluray|fullhdbluray|unknown");
 endOptGroup("quality");
 addOptGroup("quality", "Inital (Custom)");
 addList("quality", "SD TV", "&initial=sdtv", "quality-archive");
 addList("quality", "SD DVD", "&initial=sddvd", "quality-archive");
 addList("quality", "HD TV", "&initial=hdtv", "quality-archive");
+addList("quality", "RawHD TV", "&initial=rawhdtv", "quality-archive");
+addList("quality", "1080p HD TV", "&initial=fullhdtv", "quality-archive");
 addList("quality", "720p Web-DL", "&initial=hdwebdl", "quality-archive");
+addList("quality", "1080p Web-DL", "&initial=fullhdwebdl", "quality-archive");
 addList("quality", "720p BluRay", "&initial=hdbluray", "quality-archive");
 addList("quality", "1080p BluRay", "&initial=fullhdbluray", "quality-archive");
 addList("quality", "Unknown", "&initial=unknown", "quality-archive");
@@ -211,10 +216,12 @@ addOption("quality-archive", "Optional Param", "", 1);
 addOptGroup("quality-archive", "Archive (Custom)");
 addList("quality-archive", "SD DVD", "&archive=sddvd");
 addList("quality-archive", "HD TV", "&archive=hdtv");
+addList("quality-archive", "RawHD TV", "&archive=rawhdtv");
+addList("quality-archive", "1080p HD TV", "&archive=fullhdtv");
 addList("quality-archive", "720p Web-DL", "&archive=hdwebdl");
+addList("quality-archive", "1080p Web-DL", "&archive=fullhdwebdl");
 addList("quality-archive", "720p BluRay", "&archive=hdbluray");
 addList("quality-archive", "1080p BluRay", "&archive=fullhdbluray");
-addList("quality-archive", "Unknown", "&archive=unknown");
 endOptGroup("quality-archive");
 addOptGroup("quality-archive", "Random (Custom)");
 addList("quality-archive", "HD TV/1080p BluRay", "&archive=hdtv|fullhdbluray");
diff --git a/data/interfaces/default/comingEpisodes.tmpl b/data/interfaces/default/comingEpisodes.tmpl
index 0afd8412323184de65025d576cac3b2fc8170702..40396d4b223f5ef20bbcd32a12a9468995fe656e 100644
--- a/data/interfaces/default/comingEpisodes.tmpl
+++ b/data/interfaces/default/comingEpisodes.tmpl
@@ -43,7 +43,7 @@
         return false;
     },
     format: function(s) {
-        return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); 
+        return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('best',0).replace('custom',7);
     },
     type: 'numeric'
 });
diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl
index 826cf0ad58be0af72711b9e42b7046f5ca8ef2c2..2144a0fdd2f717654d27c525322b8ab31a668c5f 100755
--- a/data/interfaces/default/config_notifications.tmpl
+++ b/data/interfaces/default/config_notifications.tmpl
@@ -66,6 +66,13 @@
                                     <span class="component-desc">Fall back to a full library update if per-show fails?</span>
                                 </label>
                             </div>
+                            <div class="field-pair">
+                                <input type="checkbox" name="xbmc_update_onlyfirst" id="xbmc_update_onlyfirst" #if $sickbeard.XBMC_UPDATE_ONLYFIRST then "checked=\"checked\"" else ""# />
+                                <label class="clearfix" for="xbmc_update_onlyfirst">
+                                    <span class="component-title">Only Update First Host</span>
+                                    <span class="component-desc">Only send library update to the first host?</span>
+                                </label>
+                            </div>
                             <div class="field-pair">
                                 <label class="nocheck clearfix">
                                     <span class="component-title">XBMC IP:Port</span>
@@ -77,7 +84,7 @@
                                 </label>
                                 <label class="nocheck clearfix">
                                     <span class="component-title">&nbsp;</span>
-                                    <span class="component-desc">(multiple host strings must be separated by commas, <br/> the library updates will only be sent to the first host listed)</span>
+                                    <span class="component-desc">(multiple host strings must be separated by commas)</span>
                                 </label>
                             </div>
                             <div class="field-pair">
@@ -263,6 +270,91 @@
                     </fieldset>
                 </div><!-- /nmj component-group //-->
 
+                <div class="component-group clearfix">
+                    <div class="component-group-desc">
+                        <img class="notifier-icon" src="$sbRoot/images/notifiers/nmj.png" alt="" title="Networked Media Jukebox V2" />
+                        <h3><a href="http://www.popcornhour.com/" onclick="window.open(this.href, '_blank'); return false;">NMJv2</a></h3>
+                        <p>The Networked Media Jukebox, or NMJv2, is the official media jukebox interface made available for the Popcorn Hour 300 &amp; 400-series.</p>
+                    </div>
+                    <fieldset class="component-group-list">
+                        <div class="field-pair">
+                            <input type="checkbox" class="enabler" name="use_nmjv2" id="use_nmjv2" #if $sickbeard.USE_NMJv2 then "checked=\"checked\"" else ""# />
+                            <label class="clearfix" for="use_nmjv2">
+                                <span class="component-title">Enable</span>
+                                <span class="component-desc">Should Sick Beard send update commands to NMJv2?</span>
+                            </label>
+                        </div>
+
+                        <div id="content_use_nmjv2">
+                            <div class="field-pair">
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">Popcorn IP address</span>
+                                    <input type="text" name="nmjv2_host" id="nmjv2_host" value="$sickbeard.NMJv2_HOST" size="35" />
+                                </label>
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">&nbsp;</span>
+                                    <span class="component-desc">IP of Popcorn 300/400-series (eg. 192.168.1.100)</span>
+                                </label>
+                            </div>
+                            <div class="field-pair">
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">Database Location</span>
+                                    <span class="component-desc">
+                                        <input type="radio" name="nmjv2_dbloc" value="local" class="radio" id="nmjv2_dbloc_a" #if $sickbeard.NMJv2_DBLOC=="local" then "checked=\"checked\"" else ""# />PCH Local Media<br />
+                                    </span>
+                                </label>
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">&nbsp;</span>
+                                    <span class="component-desc">
+                                        <input type="radio" name="nmjv2_dbloc" value="network" class="radio" id="nmjv2_dbloc_b" #if $sickbeard.NMJv2_DBLOC=="network" then "checked=\"checked\"" else ""# />PCH Network Media<br />
+                                    </span>
+                                </label>
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">Database Instance</span>
+                                    <span class="component-desc">
+                                        <select id="NMJv2db_instance">
+                                            <option value="0">#1 </option>
+                                            <option value="1">#2 </option>
+                                            <option value="2">#3 </option>
+                                            <option value="3">#4 </option>
+                                            <option value="4">#5 </option>
+                                            <option value="5">#6 </option>
+                                            <option value="6">#7 </option>
+                                        </select>
+                                    </span>
+                                </label>
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">&nbsp;</span>
+                                    <span class="component-desc">Adjust this value if the wrong database is selected.</span>
+                                </label>
+                            </div>
+                            <div class="field-pair">
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">Find Database</span>
+                                    <input type="button" class="btn" value="Find Database" id="settingsNMJv2" />
+                                </label>
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">&nbsp;</span>
+                                    <span class="component-desc">The Popcorn Hour device must be powered on.</span>
+                                </label>
+                            </div>
+                            <div class="field-pair">
+                                <label class="nocheck clearfix">
+                                <span class="component-title">NMJv2 Database</span>
+                                <input type="text" name="nmjv2_database" id="nmjv2_database" value="$sickbeard.NMJv2_DATABASE" size="35" #if $sickbeard.NMJv2_DATABASE then "readonly=\"readonly\"" else ""# /></label>
+                                <label class="nocheck clearfix">
+                                    <span class="component-title">&nbsp;</span>
+                                    <span class="component-desc">Automatically filled via the 'Find Database' buttons.</span>
+                                </label>
+                            </div>
+                        <div class="testNotification" id="testNMJv2-result">Click below to test.</div>
+                        <input type="button" class="btn" value="Test NMJv2" id="testNMJv2" />
+                        <input type="submit" class="btn config_submitter" value="Save Changes" />
+                        </div><!-- /content_use_nmjv2 //-->
+
+                    </fieldset>
+                </div><!-- /nmjv2 component-group //-->
+
 
                 <div class="component-group clearfix">
                     <div class="component-group-desc">
@@ -273,7 +365,7 @@
 
                     <fieldset class="component-group-list">
                         <div class="field-pair">
-                            <input type="checkbox" class="enabler" name="use_synoindex" id="use_synoindex" #if $sickbeard.USE_SYNOINDEX then "checked=\"checked\"" else ""# /> 
+                            <input type="checkbox" class="enabler" name="use_synoindex" id="use_synoindex" #if $sickbeard.USE_SYNOINDEX then "checked=\"checked\"" else ""# />
                             <label class="clearfix" for="use_synoindex">
                                 <span class="component-title">Enable</span>
                                 <span class="component-desc">Should Sick Beard send notifications to the synoindex daemon?<br /><br />
@@ -301,7 +393,7 @@
                     </div>
                     <fieldset class="component-group-list">
                         <div class="field-pair">
-                            <input type="checkbox" class="enabler" name="use_pytivo" id="use_pytivo" #if $sickbeard.USE_PYTIVO then "checked=\"checked\"" else ""# /> 
+                            <input type="checkbox" class="enabler" name="use_pytivo" id="use_pytivo" #if $sickbeard.USE_PYTIVO then "checked=\"checked\"" else ""# />
                             <label class="clearfix" for="use_pytivo">
                                 <span class="component-title">Enable</span>
                                 <span class="component-desc">Should Sick Beard send notifications to pyTivo?<br /><br /></span>
@@ -366,7 +458,7 @@
                     </div>
                     <fieldset class="component-group-list">
                         <div class="field-pair">
-                            <input type="checkbox" class="enabler" name="use_growl" id="use_growl" #if $sickbeard.USE_GROWL then "checked=\"checked\"" else ""# /> 
+                            <input type="checkbox" class="enabler" name="use_growl" id="use_growl" #if $sickbeard.USE_GROWL then "checked=\"checked\"" else ""# />
                             <label class="clearfix" for="use_growl">
                                 <span class="component-title">Enable</span>
                                 <span class="component-desc">Should Sick Beard send Growl notifications?</span>
@@ -375,7 +467,7 @@
 
                         <div id="content_use_growl">
                             <div class="field-pair">
-                                <input type="checkbox" name="growl_notify_onsnatch" id="growl_notify_onsnatch" #if $sickbeard.GROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> 
+                                <input type="checkbox" name="growl_notify_onsnatch" id="growl_notify_onsnatch" #if $sickbeard.GROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
                                 <label class="clearfix" for="growl_notify_onsnatch">
                                     <span class="component-title">Notify on Snatch</span>
                                     <span class="component-desc">Send notification when we start a download?</span>
@@ -429,7 +521,7 @@
                     </div>
                     <fieldset class="component-group-list">
                         <div class="field-pair">
-                            <input type="checkbox" class="enabler" name="use_prowl" id="use_prowl" #if $sickbeard.USE_PROWL then "checked=\"checked\"" else ""# /> 
+                            <input type="checkbox" class="enabler" name="use_prowl" id="use_prowl" #if $sickbeard.USE_PROWL then "checked=\"checked\"" else ""# />
                             <label class="clearfix" for="use_prowl">
                                 <span class="component-title">Enable</span>
                                 <span class="component-desc">Should Sick Beard send Prowl notifications?</span>
@@ -438,7 +530,7 @@
 
                         <div id="content_use_prowl">
                             <div class="field-pair">
-                                <input type="checkbox" name="prowl_notify_onsnatch" id="prowl_notify_onsnatch" #if $sickbeard.PROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> 
+                                <input type="checkbox" name="prowl_notify_onsnatch" id="prowl_notify_onsnatch" #if $sickbeard.PROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
                                 <label class="clearfix" for="prowl_notify_onsnatch">
                                     <span class="component-title">Notify on Snatch</span>
                                     <span class="component-desc">Send notification when we start a download?</span>
@@ -553,7 +645,7 @@
                     </div>
                     <fieldset class="component-group-list">
                         <div class="field-pair">
-                            <input type="checkbox" class="enabler" name="use_libnotify" id="use_libnotify" #if $sickbeard.USE_LIBNOTIFY then "checked=\"checked\"" else ""# /> 
+                            <input type="checkbox" class="enabler" name="use_libnotify" id="use_libnotify" #if $sickbeard.USE_LIBNOTIFY then "checked=\"checked\"" else ""# />
                             <label class="clearfix" for="use_libnotify">
                                 <span class="component-title">Enable</span>
                                 <span class="component-desc">Should Sick Beard send Libnotify notifications?</span>
@@ -562,7 +654,7 @@
 
                         <div id="content_use_libnotify">
                             <div class="field-pair">
-                                <input type="checkbox" name="libnotify_notify_onsnatch" id="libnotify_notify_onsnatch" #if $sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> 
+                                <input type="checkbox" name="libnotify_notify_onsnatch" id="libnotify_notify_onsnatch" #if $sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
                                 <label class="clearfix" for="libnotify_notify_onsnatch">
                                     <span class="component-title">Notify on Snatch</span>
                                     <span class="component-desc">Send notification when we start a download?</span>
@@ -689,7 +781,7 @@
                     </div>
                     <fieldset class="component-group-list">
                         <div class="field-pair">
-                            <input type="checkbox" class="enabler" name="use_nma" id="use_nma" #if $sickbeard.USE_NMA then "checked=\"checked\"" else ""# /> 
+                            <input type="checkbox" class="enabler" name="use_nma" id="use_nma" #if $sickbeard.USE_NMA then "checked=\"checked\"" else ""# />
                             <label class="clearfix" for="use_nma">
                                 <span class="component-title">Enable</span>
                                 <span class="component-desc">Should Sick Beard send NMA notifications?</span>
@@ -698,7 +790,7 @@
 
                         <div id="content_use_nma">
                             <div class="field-pair">
-                                <input type="checkbox" name="nma_notify_onsnatch" id="nma_notify_onsnatch" #if $sickbeard.NMA_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> 
+                                <input type="checkbox" name="nma_notify_onsnatch" id="nma_notify_onsnatch" #if $sickbeard.NMA_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
                                 <label class="clearfix" for="nma_notify_onsnatch">
                                     <span class="component-title">Notify on Snatch</span>
                                     <span class="component-desc">Send notification when we start a download?</span>
@@ -775,7 +867,7 @@
 
                         <div id="content_use_twitter">
                             <div class="field-pair">
-                                <input type="checkbox" name="twitter_notify_onsnatch" id="twitter_notify_onsnatch" #if $sickbeard.TWITTER_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> 
+                                <input type="checkbox" name="twitter_notify_onsnatch" id="twitter_notify_onsnatch" #if $sickbeard.TWITTER_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# />
                                 <label class="clearfix" for="twitter_notify_onsnatch">
                                     <span class="component-title">Notify on Snatch</span>
                                     <span class="component-desc">Send notification when we start a download?</span>
diff --git a/data/interfaces/default/config_providers.tmpl b/data/interfaces/default/config_providers.tmpl
index 439913e3e209769868cdb8bbbf174f4742349f65..5231036b30b5661ee129f5565bf2c86cb2c6eec8 100755
--- a/data/interfaces/default/config_providers.tmpl
+++ b/data/interfaces/default/config_providers.tmpl
@@ -11,16 +11,17 @@
 
 <script type="text/javascript" src="$sbRoot/js/configProviders.js?$sbPID"></script>
 <script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
+#if $sickbeard.USE_NZBS
 <script type="text/javascript" charset="utf-8">
 <!--
 \$(document).ready(function(){
-    var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
 #for $curNewznabProvider in $sickbeard.newznabProviderList:
-    \$(this).addProvider('$curNewznabProvider.getID()', '$curNewznabProvider.name', '$curNewznabProvider.url', '$curNewznabProvider.key', $int($curNewznabProvider.default), show_nzb_providers);
+    \$(this).addProvider('$curNewznabProvider.getID()', '$curNewznabProvider.name', '$curNewznabProvider.url', '$curNewznabProvider.key', $int($curNewznabProvider.default));
 #end for
 });
 //-->
 </script>
+#end if
 
 <div id="config">
 <div id="config-content">
@@ -28,7 +29,7 @@
 <form id="configForm" action="saveProviders" method="post">
 
             <div id="config-components">
-                
+
                 <div id="core-component-group1" class="component-group clearfix">
 
                     <div class="component-group-desc">
@@ -78,14 +79,14 @@
                         <h3>Configure Built-In Providers</h3>
                         <p>Check with provider's website on how to obtain an API key if needed.</p>
                     </div>
-                    
+
                     <fieldset class="component-group-list">
                         <div class="field-pair">
                             <label class="clearfix" for="editAProvider">
                                 <span class="component-title jumbo">Configure Provider:</span>
                                 <span class="component-desc">
                                     #set $provider_config_list = []
-                                    #for $cur_provider in ("nzbs_r_us", "tvtorrents", "btn", "binnewz", "t411"):
+                                    #for $cur_provider in ("nzbs_r_us", "omgwtfnzbs", "tvtorrents", "torrentleech", "btn", "binnewz", "t411"):
                                         #set $cur_provider_obj = $sickbeard.providers.getProviderClass($cur_provider)
                                         #if $cur_provider_obj.providerType == $GenericProvider.NZB and not $sickbeard.USE_NZBS:
                                             #continue
@@ -95,9 +96,9 @@
                                         $provider_config_list.append($cur_provider_obj)
                                     #end for
 
-                                    #if $provider_config_list:                                        
+                                    #if $provider_config_list:
                                     <select id="editAProvider" class="input-medium" >
-                                        #for $cur_provider in $provider_config_list + [$curProvider for $curProvider in $sickbeard.newznabProviderList if $curProvider.default and $curProvider.needs_auth]:
+                                        #for $cur_provider in $provider_config_list + [$curProvider for $curProvider in $sickbeard.newznabProviderList if $curProvider.default and $curProvider.needs_auth and $sickbeard.USE_NZBS]:
                                             <option value="$cur_provider.getID()">$cur_provider.name</option>
                                         #end for
                                     </select>
@@ -130,17 +131,32 @@
                     <div class="providerDiv" id="nzbs_r_usDiv">
                         <div class="field-pair">
                             <label class="clearfix">
-                                <span class="component-title">NZBs'R'US UID</span>
+                                <span class="component-title">NZBs'R'US User ID</span>
                                 <input class="component-desc" type="text" name="nzbs_r_us_uid" value="$sickbeard.NZBSRUS_UID" size="10" />
                             </label>
                         </div>
                         <div class="field-pair">
                             <label class="clearfix">
-                                <span class="component-title">NZBs'R'US Hash</span>
+                                <span class="component-title">NZBs'R'US API Key</span>
                                 <input class="component-desc" type="text" name="nzbs_r_us_hash" value="$sickbeard.NZBSRUS_HASH" size="40" />
                             </label>
                         </div>
                     </div><!-- /nzbs_r_usDiv //-->
+                    
+                    <div class="providerDiv" id="omgwtfnzbsDiv">
+                        <div class="field-pair">
+                            <label class="clearfix">
+                                <span class="component-title">omgwtfnzbs User ID</span>
+                                <input class="component-desc" type="text" name="omgwtfnzbs_uid" value="$sickbeard.OMGWTFNZBS_UID" size="10" />
+                            </label>
+                        </div>
+                        <div class="field-pair">
+                            <label class="clearfix">
+                                <span class="component-title">omgwtfnzbs API Key</span>
+                                <input class="component-desc" type="text" name="omgwtfnzbs_key" value="$sickbeard.OMGWTFNZBS_KEY" size="40" />
+                            </label>
+                        </div>
+                    </div><!-- /omgwtfnzbsDiv //-->
 
                     <div class="providerDiv" id="binnewzDiv">
 						<p>
@@ -161,7 +177,16 @@
                                 <input class="component-desc" type="text" name="tvtorrents_hash" value="$sickbeard.TVTORRENTS_HASH" size="40" />
                             </label>
                         </div>
-                    </div><!-- /tvtorrentsDiv //-->
+                    </div><!-- /torrentleechDiv //-->
+
+                    <div class="providerDiv" id="torrentleechDiv">
+                        <div class="field-pair">
+                            <label class="clearfix">
+                                <span class="component-title">TorrentLeech RSS key (enable in profile):</span>
+                                <input class="component-desc" type="text" name="torrentleech_key" value="$sickbeard.TORRENTLEECH_KEY" size="40" />
+                            </label>
+                        </div>
+                    </div><!-- /torrentleechDiv //-->
 
                     <div class="providerDiv" id="t411Div">
                         <div class="field-pair">
@@ -190,9 +215,10 @@
 <!-- end div for editing providers -->
 
                     <input type="submit" class="btn config_submitter" value="Save Changes" /><br/>
-            
+
                     </fieldset>
                 </div><!-- /component-group2 //-->
+#if $sickbeard.USE_NZBS:
 
                 <div id="core-component-group3" class="component-group clearfix">
 
@@ -243,12 +269,12 @@
             </div>
             <div id="newznab_update_div" style="display: none;">
                 <input type="button" class="btn btn-danger newznab_delete" id="newznab_delete" value="Delete" />
-            </div> 
+            </div>
 </div>
 
                     </fieldset>
                 </div><!-- /component-group3 //-->
-
+#end if
                 <div class="component-group-save">
                     <input type="submit" class="btn config_submitter" value="Save Changes" />
                 </div><br />
diff --git a/data/interfaces/default/displayShow.tmpl b/data/interfaces/default/displayShow.tmpl
index 2cbf3da6faa86b68049c262415c9ae681ca4bc04..436c253bc0aa250920239a9e5f03d423878cc13e 100644
--- a/data/interfaces/default/displayShow.tmpl
+++ b/data/interfaces/default/displayShow.tmpl
@@ -78,10 +78,10 @@
 $qualityPresetStrings[$show.quality]
 #else:
 #if $anyQualities:
-initially download: <b><%=", ".join([Quality.qualityStrings[x] for x in anyQualities])%></b> #if $bestQualities then " + " else ""#
+initially download: <b><%=", ".join([Quality.qualityStrings[x] for x in sorted(anyQualities)])%></b> #if $bestQualities then " + " else ""#
 #end if
 #if $bestQualities:
-replace with: <b><%=", ".join([Quality.qualityStrings[x] for x in bestQualities])%></b>
+replace with: <b><%=", ".join([Quality.qualityStrings[x] for x in sorted(bestQualities)])%></b>
 #end if 
 #end if
     </td></tr>
@@ -102,7 +102,7 @@ replace with: <b><%=", ".join([Quality.qualityStrings[x] for x in bestQualities]
 <div class="float-left">
 Change selected episodes to 
 <select id="statusSelect">
-#for $curStatus in [$WANTED, $SKIPPED, $ARCHIVED, $IGNORED] + $Quality.DOWNLOADED:
+#for $curStatus in [$WANTED, $SKIPPED, $ARCHIVED, $IGNORED] + sorted($Quality.DOWNLOADED):
 #if $curStatus == $DOWNLOADED:
 #continue
 #end if
diff --git a/data/interfaces/default/history.tmpl b/data/interfaces/default/history.tmpl
index ded4e5a086ec059060cbe502f704e83c113fee06..5da3b876c716a43f120678065a84508df9a75db1 100644
--- a/data/interfaces/default/history.tmpl
+++ b/data/interfaces/default/history.tmpl
@@ -70,11 +70,13 @@
         #set $provider = $providers.getProviderClass($generic.GenericProvider.makeID($hItem["provider"]))
         #if $provider != None: 
         <img src="$sbRoot/images/providers/<%=provider.imageName()%>" width="16" height="16" alt="$provider.name" title="$provider.name"/>
+        #else:
+        <img src="$sbRoot/images/providers/missing.png" width="16" height="16" alt="missing provider" title="missing provider"/>
         #end if
       #end if
     #end if
     </td>
-    <td align="center"><span class="quality $Quality.qualityStrings[$curQuality]">$Quality.qualityStrings[$curQuality]</span></td>
+    <td align="center"><span class="quality $Quality.qualityStrings[$curQuality].replace("720p","HD720p").replace("1080p","HD1080p").replace("RawHD TV", "RawHD").replace("HD TV", "HD720p")">$Quality.qualityStrings[$curQuality]</span></td>
   </tr>
 #end for
   </tbody>
diff --git a/data/interfaces/default/home.tmpl b/data/interfaces/default/home.tmpl
index 51b9a4fff6116410da44cdd017b052391bf3b204..c604285b252a6a7212fb91cb9db1136aa682be13 100644
--- a/data/interfaces/default/home.tmpl
+++ b/data/interfaces/default/home.tmpl
@@ -40,7 +40,7 @@
         return false;
     },
     format: function(s) { 
-        return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4);
+        return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('best',0).replace('custom',7);
     },
     type: 'numeric'
 });
diff --git a/data/interfaces/default/home_newShow.tmpl b/data/interfaces/default/home_newShow.tmpl
index b4aaa0690c3dffc4cf897b2a026aa9937bc260bc..73f4c7631afcd3126e1350378b3d9ce03531f482 100644
--- a/data/interfaces/default/home_newShow.tmpl
+++ b/data/interfaces/default/home_newShow.tmpl
@@ -34,7 +34,6 @@
             <select name="tvdbLang" id="tvdbLangSelect">
                 <option value="fr" selected="selected">fr</option>
             </select><b>*</b>
-            
             <input type="button" id="searchName" value="Search" class="btn" /><br /><br />
 
             <b>*</b> This will only affect the language of the retrieved metadata file contents and episode filenames.<br />
diff --git a/data/interfaces/default/inc_qualityChooser.tmpl b/data/interfaces/default/inc_qualityChooser.tmpl
index 0f574fff3a5eb96a7739cb6461527394ed96a5ed..5956b97b24d4c8f31c856d25e02dac53b5be10c1 100644
--- a/data/interfaces/default/inc_qualityChooser.tmpl
+++ b/data/interfaces/default/inc_qualityChooser.tmpl
@@ -8,8 +8,8 @@
 #set $selected = None
 <select id="qualityPreset">
 <option value="0">Custom</option>
-#for $curPreset in sorted($qualityPresets):
-<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""#>$qualityPresetStrings[$curPreset]</option>
+#for $curPreset in sorted($qualityPresets, reverse=True):
+<option value="$curPreset" #if $curPreset == $overall_quality then "selected=\"selected\"" else ""# #if $qualityPresetStrings[$curPreset].endswith("0p") then "style=\"padding-left: 15px;\"" else ""#>$qualityPresetStrings[$curPreset]</option>
 #end for
 </select>
 </span>
@@ -36,7 +36,7 @@
 
         <div style="text-align: center;" class="float-left">
             <h4>Archive</h4>
-            #set $bestQualityList = filter(lambda x: x > $Quality.SDTV, $Quality.qualityStrings)
+            #set $bestQualityList = filter(lambda x: x > $Quality.SDTV and x < $Quality.UNKNOWN, $Quality.qualityStrings)
             <select id="bestQualities" name="bestQualities" multiple="multiple" size="$len($bestQualityList)">
             #for $curQuality in sorted($bestQualityList):
                 <option value="$curQuality" #if $curQuality in $bestQualities then "selected=\"selected\"" else ""#>$Quality.qualityStrings[$curQuality]</option>
diff --git a/data/interfaces/default/manage.tmpl b/data/interfaces/default/manage.tmpl
index 4d7035c4b9ea03a2376c233a2593425f1567c260..4be920bdb933a77ae23425d7cc9bd975fea5627a 100644
--- a/data/interfaces/default/manage.tmpl
+++ b/data/interfaces/default/manage.tmpl
@@ -26,7 +26,7 @@
         return false;
     },
     format: function(s) { 
-        return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); 
+        return s.replace('hd1080p',5).replace('hd720p',4).replace('hd',3).replace('sd',2).replace('any',1).replace('best',0).replace('custom',7);
     },
     type: 'numeric'
 });
diff --git a/data/interfaces/default/manage_massEdit.tmpl b/data/interfaces/default/manage_massEdit.tmpl
index e69b9177c818c9b813a402fc61b50cb0f3fab76e..735bbfae7f8e839423404bf9c97698d91a874f6d 100644
--- a/data/interfaces/default/manage_massEdit.tmpl
+++ b/data/interfaces/default/manage_massEdit.tmpl
@@ -60,7 +60,7 @@
         </div>
         <div style="width: 50%; text-align: center;" class="float-left">
         <h4>Archive</h4>
-            #set $bestQualityList = filter(lambda x: x > $common.Quality.SDTV, $common.Quality.qualityStrings)
+            #set $bestQualityList = filter(lambda x: x > $common.Quality.SDTV and x < $common.Quality.UNKNOWN, $common.Quality.qualityStrings)
             <select id="bestQualities" name="bestQualities" multiple="multiple" size="len($bestQualityList)">
             #for $curQuality in sorted($bestQualityList):
             <option value="$curQuality" #if $curQuality in $bestQualities then "selected=\"selected\"" else ""#>$common.Quality.qualityStrings[$curQuality]</option>
diff --git a/data/js/ajaxEpSearch.js b/data/js/ajaxEpSearch.js
index cda70b6c55f187d19811586b779b60d3382e3010..5effba384148796186ff09c0d69ff3953d331448 100644
--- a/data/js/ajaxEpSearch.js
+++ b/data/js/ajaxEpSearch.js
@@ -1,50 +1,50 @@
-(function(){
-
-	$.ajaxEpSearch = {
-	    defaults: {
-	        size:				16,
-	        colorRow:         	false,
-	        loadingImage:		'loading16_dddddd.gif',
-	        noImage:			'no16.png',
-	        yesImage:			'yes16.png'
-	    }
-	};
-
-	$.fn.ajaxEpSearch = function(options){
-		options = $.extend({}, $.ajaxEpSearch.defaults, options);
-		
-	    $('.epSearch').click(function(){
-	        var parent = $(this).parent();
-	        
-	        // put the ajax spinner (for non white bg) placeholder while we wait
-	        parent.empty();
-	        parent.append($("<img/>").attr({"src": sbRoot+"/images/"+options.loadingImage, "height": options.size, "alt": "", "title": "loading"}));
-	        
-	        $.getJSON($(this).attr('href'), function(data){
-	            // if they failed then just put the red X
-	            if (data.result == 'failure') {
-	                img_name = options.noImage;
-	                img_result = 'failed';
-
-	            // if the snatch was successful then apply the corresponding class and fill in the row appropriately
-	            } else {
-	                img_name = options.yesImage;
-	                img_result = 'success';
-	                // color the row
-	                if (options.colorRow)
-	                	parent.parent().removeClass('skipped wanted qual good unaired').addClass('good');
-	                
-	                // update the status column if it exists
-	                parent.siblings('.status_column').html(data.result);
-	            }
-
-	            // put the corresponding image as the result for the the row
-	            parent.empty();
-	            parent.append($("<img/>").attr({"src": sbRoot+"/images/"+img_name, "height": options.size, "alt": img_result, "title": img_result}));
-	        });
-
-	        // fon't follow the link
-	        return false;
-	    });
-	}
+(function () {
+
+    $.ajaxEpSearch = {
+        defaults: {
+            size:               16,
+            colorRow:           false,
+            loadingImage:       'loading16_dddddd.gif',
+            noImage:            'no16.png',
+            yesImage:           'yes16.png'
+        }
+    };
+
+    $.fn.ajaxEpSearch = function (options) {
+        options = $.extend({}, $.ajaxEpSearch.defaults, options);
+
+        $('.epSearch').click(function () {
+            var parent = $(this).parent();
+
+            // put the ajax spinner (for non white bg) placeholder while we wait
+            parent.empty();
+            parent.append($("<img/>").attr({"src": sbRoot + "/images/" + options.loadingImage, "height": options.size, "alt": "", "title": "loading"}));
+
+            $.getJSON($(this).attr('href'), function (data) {
+                // if they failed then just put the red X
+                if (data.result == 'failure') {
+                    img_name = options.noImage;
+                    img_result = 'failed';
+
+                // if the snatch was successful then apply the corresponding class and fill in the row appropriately
+                } else {
+                    img_name = options.yesImage;
+                    img_result = 'success';
+                    // color the row
+                    if (options.colorRow) {
+                        parent.parent().removeClass('skipped wanted qual good unaired snatched').addClass('snatched');
+                    }
+                    // update the status column if it exists
+                    parent.siblings('.status_column').html(data.result);
+                }
+
+                // put the corresponding image as the result for the the row
+                parent.empty();
+                parent.append($("<img/>").attr({"src": sbRoot + "/images/" + img_name, "height": options.size, "alt": img_result, "title": img_result}));
+            });
+
+            // fon't follow the link
+            return false;
+        });
+    };
 })();
diff --git a/data/js/configNotifications.js b/data/js/configNotifications.js
index a9ed18fe89ab90b22dee734f49f2e014562cd9e4..363d231ec79b7087335b3f9d58935743a6b44d68 100644
--- a/data/js/configNotifications.js
+++ b/data/js/configNotifications.js
@@ -1,89 +1,91 @@
-$(document).ready(function(){
-    var loading = '<img src="'+sbRoot+'/images/loading16.gif" height="16" width="16" />';
+$(document).ready(function () {
+    var loading = '<img src="' + sbRoot + '/images/loading16.gif" height="16" width="16" />';
 
-    $('#testGrowl').click(function(){
+    $('#testGrowl').click(function () {
         $('#testGrowl-result').html(loading);
         var growl_host = $("#growl_host").val();
         var growl_password = $("#growl_password").val();
-        var growl_result = $.get(sbRoot+"/home/testGrowl", {'host': growl_host, 'password': growl_password}, 
-        function (data){ $('#testGrowl-result').html(data); });
+        $.get(sbRoot + "/home/testGrowl", {'host': growl_host, 'password': growl_password},
+            function (data) { $('#testGrowl-result').html(data); });
     });
 
-    $('#testProwl').click(function(){
+    $('#testProwl').click(function () {
         $('#testProwl-result').html(loading);
         var prowl_api = $("#prowl_api").val();
         var prowl_priority = $("#prowl_priority").val();
-        var prowl_result = $.get(sbRoot+"/home/testProwl", {'prowl_api': prowl_api, 'prowl_priority': prowl_priority}, 
-        function (data){ $('#testProwl-result').html(data); });
+        $.get(sbRoot + "/home/testProwl", {'prowl_api': prowl_api, 'prowl_priority': prowl_priority},
+            function (data) { $('#testProwl-result').html(data); });
     });
 
-    $('#testXBMC').click(function(){
+    $('#testXBMC').click(function () {
+        $("#testXBMC").attr("disabled", true);
         $('#testXBMC-result').html(loading);
         var xbmc_host = $("#xbmc_host").val();
         var xbmc_username = $("#xbmc_username").val();
         var xbmc_password = $("#xbmc_password").val();
-        
-        $.get(sbRoot+"/home/testXBMC", {'host': xbmc_host, 'username': xbmc_username, 'password': xbmc_password}, 
-        function (data){ $('#testXBMC-result').html(data); });
+        $.get(sbRoot + "/home/testXBMC", {'host': xbmc_host, 'username': xbmc_username, 'password': xbmc_password})
+            .done(function (data) {
+                $('#testXBMC-result').html(data);
+                $("#testXBMC").attr("disabled", false);
+            });
     });
 
-    $('#testPLEX').click(function(){
+    $('#testPLEX').click(function () {
         $('#testPLEX-result').html(loading);
         var plex_host = $("#plex_host").val();
         var plex_username = $("#plex_username").val();
         var plex_password = $("#plex_password").val();
-        
-        $.get(sbRoot+"/home/testPLEX", {'host': plex_host, 'username': plex_username, 'password': plex_password}, 
-        function (data){ $('#testPLEX-result').html(data);});
+        $.get(sbRoot + "/home/testPLEX", {'host': plex_host, 'username': plex_username, 'password': plex_password},
+            function (data) { $('#testPLEX-result').html(data); });
     });
 
-    $('#testNotifo').click(function(){
+    $('#testNotifo').click(function () {
         $('#testNotifo-result').html(loading);
         var notifo_username = $("#notifo_username").val();
         var notifo_apisecret = $("#notifo_apisecret").val();
-        $.get(sbRoot+"/home/testNotifo", {'username': notifo_username, 'apisecret': notifo_apisecret},
-        function (data){ $('#testNotifo-result').html(data); });
+        $.get(sbRoot + "/home/testNotifo", {'username': notifo_username, 'apisecret': notifo_apisecret},
+            function (data) { $('#testNotifo-result').html(data); });
     });
 
-    $('#testBoxcar').click(function(){
+    $('#testBoxcar').click(function () {
         $('#testBoxcar-result').html(loading);
         var boxcar_username = $("#boxcar_username").val();
-        $.get(sbRoot+"/home/testBoxcar", {'username': boxcar_username},
-        function (data){ $('#testBoxcar-result').html(data); });
+        $.get(sbRoot + "/home/testBoxcar", {'username': boxcar_username},
+            function (data) { $('#testBoxcar-result').html(data); });
     });
 
-    $('#testPushover').click(function(){
+    $('#testPushover').click(function () {
         $('#testPushover-result').html(loading);
         var pushover_userkey = $("#pushover_userkey").val();
-        $.get(sbRoot+"/home/testPushover", {'userKey': pushover_userkey},
-        function (data){ $('#testPushover-result').html(data); });
+        $.get(sbRoot + "/home/testPushover", {'userKey': pushover_userkey},
+            function (data) { $('#testPushover-result').html(data); });
     });
 
-    $('#testLibnotify').click(function(){
+    $('#testLibnotify').click(function () {
         $('#testLibnotify-result').html(loading);
-        $.get(sbRoot+"/home/testLibnotify",
-        function(message){ $('#testLibnotify-result').html(message); });
+        $.get(sbRoot + "/home/testLibnotify",
+            function (data) { $('#testLibnotify-result').html(data); });
     });
-  
-    $('#twitterStep1').click(function(){
+
+    $('#twitterStep1').click(function () {
         $('#testTwitter-result').html(loading);
-        var twitter1_result = $.get(sbRoot+"/home/twitterStep1", function (data){window.open(data)})
-        .complete(function() { $('#testTwitter-result').html('<b>Step1:</b> Confirm Authorization'); });
+        $.get(sbRoot + "/home/twitterStep1", function (data) {window.open(data); })
+            .done(function () { $('#testTwitter-result').html('<b>Step1:</b> Confirm Authorization'); });
     });
 
-    $('#twitterStep2').click(function(){
+    $('#twitterStep2').click(function () {
         $('#testTwitter-result').html(loading);
         var twitter_key = $("#twitter_key").val();
-        $.get(sbRoot+"/home/twitterStep2", {'key': twitter_key}, 
-        function (data){ $('#testTwitter-result').html(data); });
+        $.get(sbRoot + "/home/twitterStep2", {'key': twitter_key},
+            function (data) { $('#testTwitter-result').html(data); });
     });
 
-    $('#testTwitter').click(function(){
-        $.get(sbRoot+"/home/testTwitter", 
-        function (data){ $('#testTwitter-result').html(data); });
+    $('#testTwitter').click(function () {
+        $.get(sbRoot + "/home/testTwitter",
+            function (data) { $('#testTwitter-result').html(data); });
     });
 
-    $('#settingsNMJ').click(function(){
+    $('#settingsNMJ').click(function () {
         if (!$('#nmj_host').val()) {
             alert('Please fill in the Popcorn IP address');
             $('#nmj_host').focus();
@@ -91,55 +93,99 @@ $(document).ready(function(){
         }
         $('#testNMJ-result').html(loading);
         var nmj_host = $('#nmj_host').val();
-        
-        $.get(sbRoot+"/home/settingsNMJ", {'host': nmj_host}, 
+
+        $.get(sbRoot + "/home/settingsNMJ", {'host': nmj_host},
+            function (data) {
+                if (data === null) {
+                    $('#nmj_database').removeAttr('readonly');
+                    $('#nmj_mount').removeAttr('readonly');
+                }
+                var JSONData = $.parseJSON(data);
+                $('#testNMJ-result').html(JSONData.message);
+                $('#nmj_database').val(JSONData.database);
+                $('#nmj_mount').val(JSONData.mount);
+
+                if (JSONData.database) {
+                    $('#nmj_database').attr('readonly', true);
+                } else {
+                    $('#nmj_database').removeAttr('readonly');
+                }
+                if (JSONData.mount) {
+                    $('#nmj_mount').attr('readonly', true);
+                } else {
+                    $('#nmj_mount').removeAttr('readonly');
+                }
+            });
+    });
+
+    $('#testNMJ').click(function () {
+        $('#testNMJ-result').html(loading);
+        var nmj_host = $("#nmj_host").val();
+        var nmj_database = $("#nmj_database").val();
+        var nmj_mount = $("#nmj_mount").val();
+
+        $.get(sbRoot + "/home/testNMJ", {'host': nmj_host, 'database': nmj_database, 'mount': nmj_mount},
+            function (data) { $('#testNMJ-result').html(data); });
+    });
+
+    $('#settingsNMJv2').click(function () {
+        if (!$('#nmjv2_host').val()) {
+            alert('Please fill in the Popcorn IP address');
+            $('#nmjv2_host').focus();
+            return;
+        }
+        $('#testNMJv2-result').html(loading);
+        var nmjv2_host = $('#nmjv2_host').val();
+        var nmjv2_dbloc;
+        var radios = document.getElementsByName("nmjv2_dbloc");
+        for (var i = 0; i < radios.length; i++) {
+            if (radios[i].checked) {
+                nmjv2_dbloc=radios[i].value;
+                break;
+            }
+        }
+
+        var nmjv2_dbinstance=$('#NMJv2db_instance').val();
+        $.get(sbRoot + "/home/settingsNMJv2", {'host': nmjv2_host, 'dbloc': nmjv2_dbloc, 'instance': nmjv2_dbinstance},
         function (data){
             if (data == null) {
-                $('#nmj_database').removeAttr('readonly');
-                $('#nmj_mount').removeAttr('readonly');
+                $('#nmjv2_database').removeAttr('readonly');
             }
             var JSONData = $.parseJSON(data);
-            $('#testNMJ-result').html(JSONData.message);
-            $('#nmj_database').val(JSONData.database);
-            $('#nmj_mount').val(JSONData.mount);
+            $('#testNMJv2-result').html(JSONData.message);
+            $('#nmjv2_database').val(JSONData.database);
             
-            if (JSONData.database)
-                $('#nmj_database').attr('readonly', true);
-            else
-                $('#nmj_database').removeAttr('readonly');
-            
-            if (JSONData.mount)
-                $('#nmj_mount').attr('readonly', true);
-            else
-                $('#nmj_mount').removeAttr('readonly');
+            if (JSONData.database) {
+                $('#nmjv2_database').attr('readonly', true);
+            } else {
+                $('#nmjv2_database').removeAttr('readonly');
+            }
         });
     });
 
-    $('#testNMJ').click(function(){
-        $('#testNMJ-result').html(loading);
-        var nmj_host = $("#nmj_host").val();
-        var nmj_database = $("#nmj_database").val();
-        var nmj_mount = $("#nmj_mount").val();
+    $('#testNMJv2').click(function () {
+        $('#testNMJv2-result').html(loading);
+        var nmjv2_host = $("#nmjv2_host").val();
         
-        $.get(sbRoot+"/home/testNMJ", {'host': nmj_host, 'database': nmj_database, 'mount': nmj_mount}, 
-        function (data){ $('#testNMJ-result').html(data); });
+        $.get(sbRoot + "/home/testNMJv2", {'host': nmjv2_host},
+            function (data){ $('#testNMJv2-result').html(data); });
     });
 
-    $('#testTrakt').click(function(){
+    $('#testTrakt').click(function () {
         $('#testTrakt-result').html(loading);
         var trakt_api = $("#trakt_api").val();
         var trakt_username = $("#trakt_username").val();
         var trakt_password = $("#trakt_password").val();
 
-        $.get(sbRoot+"/home/testTrakt", {'api': trakt_api, 'username': trakt_username, 'password': trakt_password},
-        function (data){ $('#testTrakt-result').html(data); });
+        $.get(sbRoot + "/home/testTrakt", {'api': trakt_api, 'username': trakt_username, 'password': trakt_password},
+            function (data) { $('#testTrakt-result').html(data); });
     });
 
-    $('#testNMA').click(function(){
+    $('#testNMA').click(function () {
         $('#testNMA-result').html(loading);
         var nma_api = $("#nma_api").val();
         var nma_priority = $("#nma_priority").val();
-        var nma_result = $.get(sbRoot+"/home/testNMA", {'nma_api': nma_api, 'nma_priority': nma_priority}, 
-        function (data){ $('#testNMA-result').html(data); });
+        $.get(sbRoot + "/home/testNMA", {'nma_api': nma_api, 'nma_priority': nma_priority},
+            function (data) { $('#testNMA-result').html(data); });
     });
 });
diff --git a/data/js/configProviders.js b/data/js/configProviders.js
index b54f63e3df511d733e025a15ed1f3409dc32002c..07817fb1ca28a96975e94f5f0ad810add85b8c17 100644
--- a/data/js/configProviders.js
+++ b/data/js/configProviders.js
@@ -13,7 +13,7 @@ $(document).ready(function(){
         });
     } 
 
-    $.fn.addProvider = function (id, name, url, key, isDefault, showProvider) {
+    $.fn.addProvider = function (id, name, url, key, isDefault) {
 
         if (url.match('/$') == null)
             url = url + '/'
@@ -27,7 +27,7 @@ $(document).ready(function(){
             $(this).populateNewznabSection();
         }
 
-        if ($('#providerOrderList > #'+id).length == 0 && showProvider != false) {
+        if ($('#providerOrderList > #'+id).length == 0) {
             var toAdd = '<li class="ui-state-default" id="'+id+'"> <input type="checkbox" id="enable_'+id+'" class="provider_enabler" CHECKED> <a href="'+url+'" class="imgLink" target="_new"><img src="'+sbRoot+'/images/providers/newznab.gif" alt="'+name+'" width="16" height="16"></a> '+name+'</li>'
 
             $('#providerOrderList').append(toAdd);
@@ -136,16 +136,15 @@ $(document).ready(function(){
 
     });
     
-    $('#newznab_key').change(function(){
+    $('#newznab_key,#newznab_url').change(function(){
         
         var selectedProvider = $('#editANewznabProvider :selected').val();
-        
-            	if (selectedProvider == "addNewznab")
-                    		return;
-                            
+
+		if (selectedProvider == "addNewznab")
+			return;
+
         var url = $('#newznab_url').val();
         var key = $('#newznab_key').val();
-                                                    
         
         $(this).updateProvider(selectedProvider, url, key);
         
@@ -209,4 +208,4 @@ $(document).ready(function(){
 
     $("#providerOrderList").disableSelection();
 
-});
+});
\ No newline at end of file
diff --git a/data/js/displayShow.js b/data/js/displayShow.js
index c821231e1f09bd82ee54d462e6addd34cc1f63fa..9c0160f0934909de495314237f82047fcd579791 100644
--- a/data/js/displayShow.js
+++ b/data/js/displayShow.js
@@ -1,9 +1,7 @@
 $(document).ready(function(){
 
     $('#sbRoot').ajaxEpSearch({'colorRow': true});
-    
-    $("td.status_column:contains('Snatched')").parent().css("background-color", "#EBC1EA");
-    
+
     $('#seasonJump').change(function() {
         var id = $(this).val();
         if (id && id != 'jump') {
diff --git a/data/js/rootDirs.js b/data/js/rootDirs.js
index a8593270a0062aeb5f3131848b7cdfd76ebcb4b9..0e56ead8fd78042700c457db175d100936fdfd4a 100644
--- a/data/js/rootDirs.js
+++ b/data/js/rootDirs.js
@@ -1,27 +1,44 @@
-$(document).ready(function(){
-
-    function logMsg(msg) {
-        if (window.console && window.logMsg)
-            console.log(msg)
+// Avoid `console` errors in browsers that lack a console.
+(function() {
+    var method;
+    var noop = function noop() {};
+    var methods = [
+        'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
+        'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
+        'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
+        'timeStamp', 'trace', 'warn'
+    ];
+    var length = methods.length;
+    var console = (window.console = window.console || {});
+
+    while (length--) {
+        method = methods[length];
+
+        // Only stub undefined methods.
+        if (!console[method]) {
+            console[method] = noop;
+        }
     }
+}());
+
+$(document).ready(function() {
 
-    function addRootDir(path){
+    function addRootDir(path) {
         // check if it's the first one
         var is_default = false;
         if (!$('#whichDefaultRootDir').val().length)
             is_default = true;
 
         $('#rootDirs').append('<option value="'+path+'">'+path+'</option>');
-        
+
         syncOptionIDs();
-        
+
         if (is_default)
             setDefault($('#rootDirs option').attr('id'));
 
         refreshRootDirs();
-    
         $.get(sbRoot+'/config/general/saveRootDirs', { rootDirString: $('#rootDirText').val() });
-    
+
     }
 
     function editRootDir(path) {
@@ -43,7 +60,7 @@ $(document).ready(function(){
     $('#addRootDir').click(function(){$(this).nFileBrowser(addRootDir)});
     $('#editRootDir').click(function(){$(this).nFileBrowser(editRootDir, {initialDir: $("#rootDirs option:selected").val()})});
 
-    $('#deleteRootDir').click(function(){
+    $('#deleteRootDir').click(function() {
         if ($("#rootDirs option:selected").length) {
 
             var toDelete = $("#rootDirs option:selected");
@@ -56,15 +73,15 @@ $(document).ready(function(){
 
             if (newDefault) {
 
-                logMsg('new default when deleting')
-                
+                console.log('new default when deleting');
+
                 // we deleted the default so this isn't valid anymore
                 $("#whichDefaultRootDir").val('');
 
                 // if we're deleting the default and there are options left then pick a new default
                 if ($("#rootDirs option").length)
                     setDefault($('#rootDirs option').attr('id'));
-            
+
             } else if ($("#whichDefaultRootDir").val().length) {
                 var old_default_num = $("#whichDefaultRootDir").val().substr(3);
                 if (old_default_num > deleted_num)
@@ -80,12 +97,12 @@ $(document).ready(function(){
         if ($("#rootDirs option:selected").length)
             setDefault($("#rootDirs option:selected").attr('id'));
         refreshRootDirs();
-        $.get(sbRoot+'/config/general/saveRootDirs', 'rootDirString='+$('#rootDirText').val());
+        $.get(sbRoot+'/config/general/saveRootDirs', {rootDirString: $('#rootDirText').val()});
     });
 
     function setDefault(which, force){
 
-        logMsg('setting default to '+which)
+        console.log('setting default to '+which);
 
         if (which != undefined && !which.length)
             return
@@ -102,7 +119,7 @@ $(document).ready(function(){
             var old_default = $('#'+$('#whichDefaultRootDir').val());
             old_default.text(old_default.text().substring(1));
         }
-        
+
         $('#whichDefaultRootDir').val(which);
     }
 
@@ -118,7 +135,7 @@ $(document).ready(function(){
 
         if (!$("#rootDirs").length)
             return
-        
+
         var do_disable = 'true';
 
         // re-sync option ids
@@ -148,11 +165,11 @@ $(document).ready(function(){
                 dir_text += '|' + $(this).val()
         });
         log_str += 'def: '+ $('#whichDefaultRootDir').val();
-        logMsg(log_str)
+        console.log(log_str);
         
         $('#rootDirText').val(dir_text);
         $('#rootDirText').change();
-        logMsg('rootDirText: '+$('#rootDirText').val())
+        console.log('rootDirText: '+$('#rootDirText').val());
     }
 
     $('#rootDirs').click(refreshRootDirs);
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index 561422015e887b22fd00afa00f9d020b96ed6d35..f6e815939f60b13c5da407f850e135bb6644f3f8 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -30,7 +30,7 @@ from threading import Lock
 
 # apparently py2exe won't build these unless they're imported somewhere
 from sickbeard import providers, metadata
-from providers import ezrss, tvtorrents, btn, nzbsrus, newznab, womble, binnewz, t411
+from providers import ezrss, tvtorrents, torrentleech, btn, nzbsrus, newznab, womble, nzbx, omgwtfnzbs, binnewz, t411
 from sickbeard.config import CheckSection, check_setting_int, check_setting_str, ConfigMigrator
 
 from sickbeard import searchCurrent, searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser
@@ -159,6 +159,9 @@ TVTORRENTS = False
 TVTORRENTS_DIGEST = None
 TVTORRENTS_HASH = None
 
+TORRENTLEECH = False
+TORRENTLEECH_KEY = None
+
 BTN = False
 BTN_API_KEY = None
 
@@ -178,6 +181,13 @@ NZBS_HASH = None
 
 WOMBLE = False
 
+NZBX = False
+NZBX_COMPLETION = 100
+
+OMGWTFNZBS = False
+OMGWTFNZBS_UID = None
+OMGWTFNZBS_KEY = None
+
 NZBSRUS = False
 NZBSRUS_UID = None
 NZBSRUS_HASH = None
@@ -211,6 +221,7 @@ XBMC_NOTIFY_ONSNATCH = False
 XBMC_NOTIFY_ONDOWNLOAD = False
 XBMC_UPDATE_LIBRARY = False
 XBMC_UPDATE_FULL = False
+XBMC_UPDATE_ONLYFIRST = False
 XBMC_HOST = ''
 XBMC_USERNAME = None
 XBMC_PASSWORD = None
@@ -273,6 +284,11 @@ NMJ_MOUNT = None
 
 USE_SYNOINDEX = False
 
+USE_NMJv2 = False
+NMJv2_HOST = None
+NMJv2_DATABASE = None
+NMJv2_DBLOC = None
+
 USE_TRAKT = False
 TRAKT_USERNAME = None
 TRAKT_PASSWORD = None
@@ -318,13 +334,13 @@ def initialize(consoleLogging=True):
                 USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, \
                 SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_HOST, \
                 NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, currentSearchScheduler, backlogSearchScheduler, \
-                USE_XBMC, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_UPDATE_FULL, \
+                USE_XBMC, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, \
                 XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \
                 USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_API, \
                 USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_UPDATE_LIBRARY, \
                 PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, \
                 showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, showList, loadingShowList, \
-                NZBS, NZBS_UID, NZBS_HASH, EZRSS, TVTORRENTS, TVTORRENTS_DIGEST, TVTORRENTS_HASH, BTN, BTN_API_KEY, TORRENT_DIR, USENET_RETENTION, SOCKET_TIMEOUT, \
+                NZBS, NZBS_UID, NZBS_HASH, EZRSS, TVTORRENTS, TVTORRENTS_DIGEST, TVTORRENTS_HASH, BTN, BTN_API_KEY, TORRENTLEECH, TORRENTLEECH_KEY, TORRENT_DIR, USENET_RETENTION, SOCKET_TIMEOUT, \
 				BINNEWZ, \
                 T411, T411_USERNAME, T411_PASSWORD, \
                 SEARCH_FREQUENCY, DEFAULT_SEARCH_FREQUENCY, BACKLOG_SEARCH_FREQUENCY, \
@@ -338,12 +354,12 @@ def initialize(consoleLogging=True):
                 showQueueScheduler, searchQueueScheduler, ROOT_DIRS, CACHE_DIR, ACTUAL_CACHE_DIR, TVDB_API_PARMS, \
                 NAMING_PATTERN, NAMING_MULTI_EP, NAMING_FORCE_FOLDERS, NAMING_ABD_PATTERN, NAMING_CUSTOM_ABD, \
                 RENAME_EPISODES, properFinderScheduler, PROVIDER_ORDER, autoPostProcesserScheduler, \
-                NZBSRUS, NZBSRUS_UID, NZBSRUS_HASH, WOMBLE, providerList, newznabProviderList, \
+                NZBSRUS, NZBSRUS_UID, NZBSRUS_HASH, WOMBLE, NZBX, NZBX_COMPLETION, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_KEY, providerList, newznabProviderList, \
                 EXTRA_SCRIPTS, USE_TWITTER, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \
                 USE_NOTIFO, NOTIFO_USERNAME, NOTIFO_APISECRET, NOTIFO_NOTIFY_ONDOWNLOAD, NOTIFO_NOTIFY_ONSNATCH, \
                 USE_BOXCAR, BOXCAR_USERNAME, BOXCAR_PASSWORD, BOXCAR_NOTIFY_ONDOWNLOAD, BOXCAR_NOTIFY_ONSNATCH, \
                 USE_PUSHOVER, PUSHOVER_USERKEY, PUSHOVER_NOTIFY_ONDOWNLOAD, PUSHOVER_NOTIFY_ONSNATCH, \
-                USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_SYNOINDEX, \
+                USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_NMJv2, NMJv2_HOST, NMJv2_DATABASE, NMJv2_DBLOC, USE_SYNOINDEX, \
                 USE_BANNER, USE_LISTVIEW, METADATA_XBMC, METADATA_MEDIABROWSER, METADATA_PS3, METADATA_SYNOLOGY, metadata_provider_dict, \
                 NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, \
                 COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, METADATA_WDTV, METADATA_TIVO, IGNORE_WORDS, CREATE_MISSING_SHOW_DIRS, \
@@ -544,6 +560,10 @@ def initialize(consoleLogging=True):
         BTN = bool(check_setting_int(CFG, 'BTN', 'btn', 0))
         BTN_API_KEY = check_setting_str(CFG, 'BTN', 'btn_api_key', '')
 
+        CheckSection(CFG, 'TorrentLeech')
+        TORRENTLEECH = bool(check_setting_int(CFG, 'TorrentLeech', 'torrentleech', 0))
+        TORRENTLEECH_KEY = check_setting_str(CFG, 'TorrentLeech', 'torrentleech_key', '')
+
         CheckSection(CFG, 'NZBs')
         NZBS = bool(check_setting_int(CFG, 'NZBs', 'nzbs', 0))
         NZBS_UID = check_setting_str(CFG, 'NZBs', 'nzbs_uid', '')
@@ -575,6 +595,15 @@ def initialize(consoleLogging=True):
         CheckSection(CFG, 'Womble')
         WOMBLE = bool(check_setting_int(CFG, 'Womble', 'womble', 1))
 
+        CheckSection(CFG, 'nzbX')
+        NZBX = bool(check_setting_int(CFG, 'nzbX', 'nzbx', 0))
+        NZBX_COMPLETION = check_setting_int(CFG, 'nzbX', 'nzbx_completion', 100)
+
+        CheckSection(CFG, 'omgwtfnzbs')
+        OMGWTFNZBS = bool(check_setting_int(CFG, 'omgwtfnzbs', 'omgwtfnzbs', 0))
+        OMGWTFNZBS_UID = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_uid', '')
+        OMGWTFNZBS_KEY = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_key', '')
+
         CheckSection(CFG, 'SABnzbd')
         SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '')
         SAB_PASSWORD = check_setting_str(CFG, 'SABnzbd', 'sab_password', '')
@@ -593,6 +622,7 @@ def initialize(consoleLogging=True):
         XBMC_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_ondownload', 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', '')
@@ -659,6 +689,12 @@ def initialize(consoleLogging=True):
         NMJ_DATABASE = check_setting_str(CFG, 'NMJ', 'nmj_database', '')
         NMJ_MOUNT = check_setting_str(CFG, 'NMJ', 'nmj_mount', '')
 
+        CheckSection(CFG, 'NMJv2')
+        USE_NMJv2 = bool(check_setting_int(CFG, 'NMJv2', 'use_nmjv2', 0))
+        NMJv2_HOST = check_setting_str(CFG, 'NMJv2', 'nmjv2_host', '')
+        NMJv2_DATABASE = check_setting_str(CFG, 'NMJv2', 'nmjv2_database', '')
+        NMJ_DBLOC = check_setting_str(CFG, 'NMJv2', 'nmjv2_dbloc', '')
+
         CheckSection(CFG, 'Synology')
         USE_SYNOINDEX = bool(check_setting_int(CFG, 'Synology', 'use_synoindex', 0))
 
@@ -1026,6 +1062,10 @@ def save_config():
     new_config['BTN']['btn'] = int(BTN)
     new_config['BTN']['btn_api_key'] = BTN_API_KEY
 
+    new_config['TorrentLeech'] = {}
+    new_config['TorrentLeech']['torrentleech'] = int(TORRENTLEECH)
+    new_config['TorrentLeech']['torrentleech_key'] = TORRENTLEECH_KEY
+
     new_config['NZBs'] = {}
     new_config['NZBs']['nzbs'] = int(NZBS)
     new_config['NZBs']['nzbs_uid'] = NZBS_UID
@@ -1057,6 +1097,15 @@ def save_config():
     new_config['Womble'] = {}
     new_config['Womble']['womble'] = int(WOMBLE)
 
+    new_config['nzbX'] = {}
+    new_config['nzbX']['nzbx'] = int(NZBX)
+    new_config['nzbX']['nzbx_completion'] = int(NZBX_COMPLETION)
+
+    new_config['omgwtfnzbs'] = {}
+    new_config['omgwtfnzbs']['omgwtfnzbs'] = int(OMGWTFNZBS)
+    new_config['omgwtfnzbs']['omgwtfnzbs_uid'] = OMGWTFNZBS_UID
+    new_config['omgwtfnzbs']['omgwtfnzbs_key'] = OMGWTFNZBS_KEY
+
     new_config['SABnzbd'] = {}
     new_config['SABnzbd']['sab_username'] = SAB_USERNAME
     new_config['SABnzbd']['sab_password'] = SAB_PASSWORD
@@ -1075,6 +1124,7 @@ def save_config():
     new_config['XBMC']['xbmc_notify_ondownload'] = int(XBMC_NOTIFY_ONDOWNLOAD)
     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'] = XBMC_PASSWORD
@@ -1144,6 +1194,12 @@ def save_config():
     new_config['Synology'] = {}
     new_config['Synology']['use_synoindex'] = int(USE_SYNOINDEX)
 
+    new_config['NMJv2'] = {}
+    new_config['NMJv2']['use_nmjv2'] = int(USE_NMJv2)
+    new_config['NMJv2']['nmjv2_host'] = NMJv2_HOST
+    new_config['NMJv2']['nmjv2_database'] = NMJv2_DATABASE
+    new_config['NMJv2']['nmjv2_dbloc'] = NMJv2_DBLOC
+
     new_config['Trakt'] = {}
     new_config['Trakt']['use_trakt'] = int(USE_TRAKT)
     new_config['Trakt']['trakt_username'] = TRAKT_USERNAME
diff --git a/sickbeard/common.py b/sickbeard/common.py
index 3da19925091fb20a17d1ec7a21519898fcf7ddc3..3c82b0e6e9661e3d3af7245d4918ea29019c80f9 100644
--- a/sickbeard/common.py
+++ b/sickbeard/common.py
@@ -17,12 +17,13 @@
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
 import os.path
-import operator, platform
+import operator
+import platform
 import re
 
 from sickbeard import version
 
-USER_AGENT = 'Sick Beard/alpha2-'+version.SICKBEARD_VERSION.replace(' ','-')+' ('+platform.system()+' '+platform.release()+')'
+USER_AGENT = 'Sick Beard/alpha2-' + version.SICKBEARD_VERSION.replace(' ', '-') + ' (' + platform.system() + ' ' + platform.release() + ')'
 
 mediaExtensions = ['avi', 'mkv', 'mpg', 'mpeg', 'wmv',
                    'ogm', 'mp4', 'iso', 'img', 'divx',
@@ -68,25 +69,31 @@ multiEpStrings[NAMING_EXTEND] = "Extend"
 multiEpStrings[NAMING_LIMITED_EXTEND] = "Extend (Limited)"
 multiEpStrings[NAMING_LIMITED_EXTEND_E_PREFIXED] = "Extend (Limited, E-prefixed)"
 
-class Quality:
 
-    NONE = 0
-    SDTV = 1
-    SDDVD = 1<<1 # 2
-    HDTV = 1<<2 # 4
-    HDWEBDL = 1<<3 # 8
-    HDBLURAY = 1<<4 # 16
-    FULLHDBLURAY = 1<<5 # 32
+class Quality:
+    NONE = 0              # 0
+    SDTV = 1              # 1
+    SDDVD = 1 << 1        # 2
+    HDTV = 1 << 2         # 4
+    RAWHDTV = 1 << 3      # 8  -- 720p/1080i mpeg2 (trollhd releases)
+    FULLHDTV = 1 << 4     # 16 -- 1080p HDTV (QCF releases)
+    HDWEBDL = 1 << 5      # 32
+    FULLHDWEBDL = 1 << 6  # 64 -- 1080p web-dl
+    HDBLURAY = 1 << 7     # 128
+    FULLHDBLURAY = 1 << 8 # 256
 
     # put these bits at the other end of the spectrum, far enough out that they shouldn't interfere
-    UNKNOWN = 1<<15
+    UNKNOWN = 1 << 15     # 32768
 
     qualityStrings = {NONE: "N/A",
                       UNKNOWN: "Unknown",
                       SDTV: "SD TV",
                       SDDVD: "SD DVD",
                       HDTV: "HD TV",
+                      RAWHDTV: "RawHD TV",
+                      FULLHDTV: "1080p HD TV",
                       HDWEBDL: "720p WEB-DL",
+                      FULLHDWEBDL: "1080p WEB-DL",
                       HDBLURAY: "720p BluRay",
                       FULLHDBLURAY: "1080p BluRay"}
 
@@ -97,7 +104,7 @@ class Quality:
     def _getStatusStrings(status):
         toReturn = {}
         for x in Quality.qualityStrings.keys():
-            toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")"
+            toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status] + " (" + Quality.qualityStrings[x] + ")"
         return toReturn
 
     @staticmethod
@@ -108,7 +115,7 @@ class Quality:
             anyQuality = reduce(operator.or_, anyQualities)
         if bestQualities:
             bestQuality = reduce(operator.or_, bestQualities)
-        return anyQuality | (bestQuality<<16)
+        return anyQuality | (bestQuality << 16)
 
     @staticmethod
     def splitQuality(quality):
@@ -117,50 +124,56 @@ class Quality:
         for curQual in Quality.qualityStrings.keys():
             if curQual & quality:
                 anyQualities.append(curQual)
-            if curQual<<16 & quality:
+            if curQual << 16 & quality:
                 bestQualities.append(curQual)
 
         return (sorted(anyQualities), sorted(bestQualities))
 
     @staticmethod
     def nameQuality(name):
-
         name = os.path.basename(name)
 
         # if we have our exact text then assume we put it there
-        for x in Quality.qualityStrings:
+        for x in sorted(Quality.qualityStrings, reverse=True):
             if x == Quality.UNKNOWN:
                 continue
 
-            regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W'
+            regex = '\W' + Quality.qualityStrings[x].replace(' ', '\W') + '\W'
             regex_match = re.search(regex, name, re.I)
             if regex_match:
                 return x
 
         checkName = lambda list, func: func([re.search(x, name, re.I) for x in list])
 
-        if checkName(["(pdtv|hdtv|dsr|tvrip).(xvid|x264)"], all) and not checkName(["(720|1080)[pi]"], all):
+        if checkName(["(pdtv|hdtv|dsr|tvrip|webrip).(xvid|x264)"], all) and not checkName(["(720|1080)[pi]"], all):
             return Quality.SDTV
         elif checkName(["(dvdrip|bdrip)(.ws)?.(xvid|divx|x264)"], any) and not checkName(["(720|1080)[pi]"], all):
             return Quality.SDDVD
-        elif checkName(["720p", "hdtv", "x264"], all) or checkName(["hr.ws.pdtv.x264"], any):
+        elif checkName(["720p", "hdtv", "x264"], all) or checkName(["hr.ws.pdtv.x264"], any) and not checkName(["(1080)[pi]"], all):
             return Quality.HDTV
-        elif checkName(["720p", "web.dl"], all) or checkName(["720p", "itunes", "h.?264"], all):
+        elif checkName(["720p|1080i", "hdtv", "mpeg2"], all):
+            return Quality.RAWHDTV
+        elif checkName(["1080p", "hdtv", "x264"], all):
+            return Quality.FULLHDTV
+        elif checkName(["720p", "web.dl|webrip"], all) or checkName(["720p", "itunes", "h.?264"], all):
             return Quality.HDWEBDL
-        elif checkName(["720p", "bluray", "x264"], all) or checkName(["720p", "hddvd", "x264"], all):
+        elif checkName(["1080p", "web.dl|webrip"], all) or checkName(["1080p", "itunes", "h.?264"], all):
+            return Quality.FULLHDWEBDL
+        elif checkName(["720p", "bluray|hddvd", "x264"], all):
             return Quality.HDBLURAY
-        elif checkName(["1080p", "bluray", "x264"], all) or checkName(["1080p", "hddvd", "x264"], all):
+        elif checkName(["1080p", "bluray|hddvd", "x264"], all):
             return Quality.FULLHDBLURAY
         else:
             return Quality.UNKNOWN
 
     @staticmethod
     def assumeQuality(name):
-
         if name.lower().endswith((".avi", ".mp4")):
             return Quality.SDTV
         elif name.lower().endswith(".mkv"):
             return Quality.HDTV
+        elif name.lower().endswith(".ts"):
+            return Quality.RAWHDTV
         else:
             return Quality.UNKNOWN
 
@@ -174,15 +187,15 @@ class Quality:
 
     @staticmethod
     def splitCompositeStatus(status):
+        """Returns a tuple containing (status, quality)"""
         if status == UNKNOWN:
             return (UNKNOWN, Quality.UNKNOWN)
-        
-        """Returns a tuple containing (status, quality)"""
+
         for x in sorted(Quality.qualityStrings.keys(), reverse=True):
-            if status > x*100:
-                return (status-x*100, x)
+            if status > x * 100:
+                return (status - x * 100, x)
 
-        return (Quality.NONE, status)
+        return (status, Quality.NONE)
 
     @staticmethod
     def statusFromName(name, assume=True):
@@ -199,22 +212,29 @@ Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qu
 Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()]
 Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()]
 
-HD = Quality.combineQualities([Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY], [])
 SD = Quality.combineQualities([Quality.SDTV, Quality.SDDVD], [])
-ANY = Quality.combineQualities([Quality.SDTV, Quality.SDDVD, Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY, Quality.UNKNOWN], [])
+HD = Quality.combineQualities([Quality.HDTV, Quality.FULLHDTV, Quality.HDWEBDL, Quality.FULLHDWEBDL, Quality.HDBLURAY, Quality.FULLHDBLURAY], []) # HD720p + HD1080p
+HD720p = Quality.combineQualities([Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY], [])
+HD1080p = Quality.combineQualities([Quality.FULLHDTV, Quality.FULLHDWEBDL, Quality.FULLHDBLURAY], [])
+ANY = Quality.combineQualities([Quality.SDTV, Quality.SDDVD, Quality.HDTV, Quality.FULLHDTV, Quality.HDWEBDL, Quality.FULLHDWEBDL, Quality.HDBLURAY, Quality.FULLHDBLURAY, Quality.UNKNOWN], []) # SD + HD
+
+# legacy template, cant remove due to reference in mainDB upgrade?
 BEST = Quality.combineQualities([Quality.SDTV, Quality.HDTV, Quality.HDWEBDL], [Quality.HDTV])
 
-qualityPresets = (SD, HD, ANY)
+qualityPresets = (SD, HD, HD720p, HD1080p, ANY)
 qualityPresetStrings = {SD: "SD",
                         HD: "HD",
+                        HD720p: "HD720p",
+                        HD1080p: "HD1080p",
                         ANY: "Any"}
 
+
 class StatusStrings:
     def __init__(self):
         self.statusStrings = {UNKNOWN: "Unknown",
                               UNAIRED: "Unaired",
                               SNATCHED: "Snatched",
-                              DOWNLOADED:  "Downloaded",
+                              DOWNLOADED: "Downloaded",
                               SKIPPED: "Skipped",
                               SNATCHED_PROPER: "Snatched (Proper)",
                               WANTED: "Wanted",
@@ -227,7 +247,7 @@ class StatusStrings:
             if quality == Quality.NONE:
                 return self.statusStrings[status]
             else:
-                return self.statusStrings[status]+" ("+Quality.qualityStrings[quality]+")"
+                return self.statusStrings[status] + " (" + Quality.qualityStrings[quality] + ")"
         else:
             return self.statusStrings[name]
 
@@ -236,6 +256,7 @@ class StatusStrings:
 
 statusStrings = StatusStrings()
 
+
 class Overview:
     UNAIRED = UNAIRED # 1
     QUAL = 2
@@ -243,11 +264,15 @@ class Overview:
     GOOD = 4
     SKIPPED = SKIPPED # 5
 
+    # For both snatched statuses. Note: SNATCHED/QUAL have same value and break dict.
+    SNATCHED = SNATCHED_PROPER # 9
+
     overviewStrings = {SKIPPED: "skipped",
                        WANTED: "wanted",
                        QUAL: "qual",
                        GOOD: "good",
-                       UNAIRED: "unaired"}
+                       UNAIRED: "unaired",
+                       SNATCHED: "snatched"}
 
 # Get our xml namespaces correct for lxml
 XML_NSMAP = {'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
@@ -258,7 +283,6 @@ countryList = {'Australia': 'AU',
                'Canada': 'CA',
                'USA': 'US'
                }
-
 showLanguages = {'en':'english',
                  'fr':'french',
 				 '':'unknown'
diff --git a/sickbeard/databases/mainDB.py b/sickbeard/databases/mainDB.py
index da2a9c761a87300e3119e9524a9c97162635f672..01f8b032f5e44d78a7d501d58446aeeaddd49154 100644
--- a/sickbeard/databases/mainDB.py
+++ b/sickbeard/databases/mainDB.py
@@ -23,17 +23,17 @@ from sickbeard import db, common, helpers, logger
 from sickbeard.providers.generic import GenericProvider
 
 from sickbeard import encodingKludge as ek
-from sickbeard.name_parser.parser import NameParser, InvalidNameException 
+from sickbeard.name_parser.parser import NameParser, InvalidNameException
 
-class MainSanityCheck(db.DBSanityCheck):
+MAX_DB_VERSION = 12
 
+class MainSanityCheck(db.DBSanityCheck):
     def check(self):
         self.fix_duplicate_shows()
         self.fix_duplicate_episodes()
         self.fix_orphan_episodes()
 
     def fix_duplicate_shows(self):
-
         sqlResults = self.connection.select("SELECT show_id, tvdb_id, COUNT(tvdb_id) as count FROM tv_shows GROUP BY tvdb_id HAVING count > 1")
 
         for cur_duplicate in sqlResults:
@@ -41,7 +41,7 @@ class MainSanityCheck(db.DBSanityCheck):
             logger.log(u"Duplicate show detected! tvdb_id: " + str(cur_duplicate["tvdb_id"]) + u" count: " + str(cur_duplicate["count"]), logger.DEBUG)
 
             cur_dupe_results = self.connection.select("SELECT show_id, tvdb_id FROM tv_shows WHERE tvdb_id = ? LIMIT ?",
-                                           [cur_duplicate["tvdb_id"], int(cur_duplicate["count"])-1]
+                                           [cur_duplicate["tvdb_id"], int(cur_duplicate["count"]) - 1]
                                            )
 
             for cur_dupe_id in cur_dupe_results:
@@ -52,15 +52,14 @@ class MainSanityCheck(db.DBSanityCheck):
             logger.log(u"No duplicate show, check passed")
 
     def fix_duplicate_episodes(self):
-
         sqlResults = self.connection.select("SELECT showid, season, episode, COUNT(showid) as count FROM tv_episodes GROUP BY showid, season, episode HAVING count > 1")
 
         for cur_duplicate in sqlResults:
 
-            logger.log(u"Duplicate episode detected! showid: " + str(cur_duplicate["showid"]) + u" season: "+str(cur_duplicate["season"]) + u" episode: "+str(cur_duplicate["episode"]) + u" count: " + str(cur_duplicate["count"]), logger.DEBUG)
+            logger.log(u"Duplicate episode detected! showid: " + str(cur_duplicate["showid"]) + u" season: " + str(cur_duplicate["season"]) + u" episode: " + str(cur_duplicate["episode"]) + u" count: " + str(cur_duplicate["count"]), logger.DEBUG)
 
             cur_dupe_results = self.connection.select("SELECT episode_id FROM tv_episodes WHERE showid = ? AND season = ? and episode = ? ORDER BY episode_id DESC LIMIT ?",
-                                           [cur_duplicate["showid"], cur_duplicate["season"], cur_duplicate["episode"], int(cur_duplicate["count"])-1]
+                                           [cur_duplicate["showid"], cur_duplicate["season"], cur_duplicate["episode"], int(cur_duplicate["count"]) - 1]
                                            )
 
             for cur_dupe_id in cur_dupe_results:
@@ -71,17 +70,17 @@ class MainSanityCheck(db.DBSanityCheck):
             logger.log(u"No duplicate episode, check passed")
 
     def fix_orphan_episodes(self):
-
         sqlResults = self.connection.select("SELECT episode_id, showid, tv_shows.tvdb_id FROM tv_episodes LEFT JOIN tv_shows ON tv_episodes.showid=tv_shows.tvdb_id WHERE tv_shows.tvdb_id is NULL")
 
         for cur_orphan in sqlResults:
             logger.log(u"Orphan episode detected! episode_id: " + str(cur_orphan["episode_id"]) + " showid: " + str(cur_orphan["showid"]), logger.DEBUG)
-            logger.log(u"Deleting orphan episode with episode_id: "+str(cur_orphan["episode_id"]))
+            logger.log(u"Deleting orphan episode with episode_id: " + str(cur_orphan["episode_id"]))
             self.connection.action("DELETE FROM tv_episodes WHERE episode_id = ?", [cur_orphan["episode_id"]])
 
         else:
             logger.log(u"No orphan episode, check passed")
 
+
 def backupDatabase(version):
     helpers.backupVersionedFile(db.dbFilename(), version)
 
@@ -90,6 +89,7 @@ def backupDatabase(version):
 # ======================
 # Add new migrations at the bottom of the list; subclass the previous migration.
 
+
 class InitialSchema (db.SchemaUpgrade):
     def test(self):
         return self.hasTable("tv_shows")
@@ -104,6 +104,7 @@ class InitialSchema (db.SchemaUpgrade):
         for query in queries:
             self.connection.action(query)
 
+
 class AddTvrId (InitialSchema):
     def test(self):
         return self.hasColumn("tv_shows", "tvr_id")
@@ -111,6 +112,7 @@ class AddTvrId (InitialSchema):
     def execute(self):
         self.addColumn("tv_shows", "tvr_id")
 
+
 class AddTvrName (AddTvrId):
     def test(self):
         return self.hasColumn("tv_shows", "tvr_name")
@@ -118,6 +120,7 @@ class AddTvrName (AddTvrId):
     def execute(self):
         self.addColumn("tv_shows", "tvr_name", "TEXT", "")
 
+
 class AddAirdateIndex (AddTvrName):
     def test(self):
         return self.hasTable("idx_tv_episodes_showid_airdate")
@@ -125,6 +128,7 @@ class AddAirdateIndex (AddTvrName):
     def execute(self):
         self.connection.action("CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate);")
 
+
 class NumericProviders (AddAirdateIndex):
     def test(self):
         return self.connection.tableInfo("history")['provider']['type'] == 'TEXT'
@@ -157,12 +161,12 @@ class NumericProviders (AddAirdateIndex):
             args = [curResult["action"], curResult["date"], curResult["showid"], curResult["season"], curResult["episode"], curResult["quality"], curResult["resource"], provider]
             self.connection.action(sql, args)
 
+
 class NewQualitySettings (NumericProviders):
     def test(self):
         return self.hasTable("db_version")
 
     def execute(self):
-
         backupDatabase(0)
 
         # old stuff that's been removed from common but we need it to upgrade
@@ -229,7 +233,6 @@ class NewQualitySettings (NumericProviders):
         if didUpdate:
             os.remove(db.dbFilename(suffix='v0'))
 
-
         ### Update show qualities
         toUpdate = self.connection.select("SELECT * FROM tv_shows")
         for curUpdate in toUpdate:
@@ -246,13 +249,12 @@ class NewQualitySettings (NumericProviders):
             elif int(curUpdate["quality"]) == BEST:
                 newQuality = common.BEST
             else:
-                logger.log(u"Unknown show quality: "+str(curUpdate["quality"]), logger.WARNING)
+                logger.log(u"Unknown show quality: " + str(curUpdate["quality"]), logger.WARNING)
                 newQuality = None
 
             if newQuality:
                 self.connection.action("UPDATE tv_shows SET quality = ? WHERE show_id = ?", [newQuality, curUpdate["show_id"]])
 
-
         ### Update history
         toUpdate = self.connection.select("SELECT * FROM history")
         for curUpdate in toUpdate:
@@ -282,6 +284,7 @@ class NewQualitySettings (NumericProviders):
         self.connection.action("CREATE TABLE db_version (db_version INTEGER);")
         self.connection.action("INSERT INTO db_version (db_version) VALUES (?)", [1])
 
+
 class DropOldHistoryTable(NewQualitySettings):
     def test(self):
         return self.checkDBVersion() >= 2
@@ -290,12 +293,12 @@ class DropOldHistoryTable(NewQualitySettings):
         self.connection.action("DROP TABLE history_old")
         self.incDBVersion()
 
+
 class UpgradeHistoryForGenericProviders(DropOldHistoryTable):
     def test(self):
         return self.checkDBVersion() >= 3
 
     def execute(self):
-
         providerMap = {'NZBs': 'NZBs.org',
                        'BinReq': 'Bin-Req',
                        'NZBsRUS': '''NZBs'R'US''',
@@ -306,6 +309,7 @@ class UpgradeHistoryForGenericProviders(DropOldHistoryTable):
 
         self.incDBVersion()
 
+
 class AddAirByDateOption(UpgradeHistoryForGenericProviders):
     def test(self):
         return self.checkDBVersion() >= 4
@@ -314,24 +318,27 @@ class AddAirByDateOption(UpgradeHistoryForGenericProviders):
         self.connection.action("ALTER TABLE tv_shows ADD air_by_date NUMERIC")
         self.incDBVersion()
 
+
 class ChangeSabConfigFromIpToHost(AddAirByDateOption):
     def test(self):
         return self.checkDBVersion() >= 5
-    
+
     def execute(self):
         sickbeard.SAB_HOST = 'http://' + sickbeard.SAB_HOST + '/sabnzbd/'
         self.incDBVersion()
 
+
 class FixSabHostURL(ChangeSabConfigFromIpToHost):
     def test(self):
         return self.checkDBVersion() >= 6
-    
+
     def execute(self):
         if sickbeard.SAB_HOST.endswith('/sabnzbd/'):
-            sickbeard.SAB_HOST = sickbeard.SAB_HOST.replace('/sabnzbd/','/')
+            sickbeard.SAB_HOST = sickbeard.SAB_HOST.replace('/sabnzbd/', '/')
         sickbeard.save_config()
         self.incDBVersion()
 
+
 class AddLang (FixSabHostURL):
     def test(self):
         return self.hasColumn("tv_shows", "lang")
@@ -349,10 +356,10 @@ class AddCustomSearchNames (AddLang):
 class PopulateRootDirs (AddCustomSearchNames):
     def test(self):
         return self.checkDBVersion() >= 7
-    
+
     def execute(self):
         dir_results = self.connection.select("SELECT location FROM tv_shows")
-        
+
         dir_counts = {}
         for cur_dir in dir_results:
             cur_root_dir = ek.ek(os.path.dirname, ek.ek(os.path.normpath, cur_dir["location"]))
@@ -360,31 +367,30 @@ class PopulateRootDirs (AddCustomSearchNames):
                 dir_counts[cur_root_dir] = 1
             else:
                 dir_counts[cur_root_dir] += 1
-        
-        logger.log(u"Dir counts: "+str(dir_counts), logger.DEBUG)
-        
+
+        logger.log(u"Dir counts: " + str(dir_counts), logger.DEBUG)
+
         if not dir_counts:
             self.incDBVersion()
             return
-        
+
         default_root_dir = dir_counts.values().index(max(dir_counts.values()))
-        
-        new_root_dirs = str(default_root_dir)+'|'+'|'.join(dir_counts.keys())
-        logger.log(u"Setting ROOT_DIRS to: "+new_root_dirs, logger.DEBUG)
-        
+
+        new_root_dirs = str(default_root_dir) + '|' + '|'.join(dir_counts.keys())
+        logger.log(u"Setting ROOT_DIRS to: " + new_root_dirs, logger.DEBUG)
+
         sickbeard.ROOT_DIRS = new_root_dirs
-        
+
         sickbeard.save_config()
-        
+
         self.incDBVersion()
-        
-class SetNzbTorrentSettings(PopulateRootDirs):
 
+
+class SetNzbTorrentSettings(PopulateRootDirs):
     def test(self):
         return self.checkDBVersion() >= 8
-    
-    def execute(self):
 
+    def execute(self):
         use_torrents = False
         use_nzbs = False
 
@@ -392,33 +398,32 @@ class SetNzbTorrentSettings(PopulateRootDirs):
             if cur_provider.isEnabled():
                 if cur_provider.providerType == GenericProvider.NZB:
                     use_nzbs = True
-                    logger.log(u"Provider "+cur_provider.name+" is enabled, enabling NZBs in the upgrade")
+                    logger.log(u"Provider " + cur_provider.name + " is enabled, enabling NZBs in the upgrade")
                     break
                 elif cur_provider.providerType == GenericProvider.TORRENT:
                     use_torrents = True
-                    logger.log(u"Provider "+cur_provider.name+" is enabled, enabling Torrents in the upgrade")
+                    logger.log(u"Provider " + cur_provider.name + " is enabled, enabling Torrents in the upgrade")
                     break
 
         sickbeard.USE_TORRENTS = use_torrents
         sickbeard.USE_NZBS = use_nzbs
-        
+
         sickbeard.save_config()
-        
+
         self.incDBVersion()
 
+
 class FixAirByDateSetting(SetNzbTorrentSettings):
-    
     def test(self):
         return self.checkDBVersion() >= 9
 
     def execute(self):
-        
         shows = self.connection.select("SELECT * FROM tv_shows")
-        
+
         for cur_show in shows:
             if cur_show["genre"] and "talk show" in cur_show["genre"].lower():
                 self.connection.action("UPDATE tv_shows SET air_by_date = ? WHERE tvdb_id = ?", [1, cur_show["tvdb_id"]])
-        
+
         self.incDBVersion()
         
 class AddAudioLang (FixAirByDateSetting):
@@ -438,9 +443,8 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode):
 
     def test(self):
         return self.checkDBVersion() >= 10
-    
-    def execute(self):
 
+    def execute(self):
         backupDatabase(10)
 
         if not self.hasColumn("tv_episodes", "file_size"):
@@ -450,12 +454,12 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode):
             self.addColumn("tv_episodes", "release_name", "TEXT", "")
 
         ep_results = self.connection.select("SELECT episode_id, location, file_size FROM tv_episodes")
-        
+
         logger.log(u"Adding file size to all episodes in DB, please be patient")
         for cur_ep in ep_results:
             if not cur_ep["location"]:
                 continue
-            
+
             # if there is no size yet then populate it for us
             if (not cur_ep["file_size"] or not int(cur_ep["file_size"])) and ek.ek(os.path.isfile, cur_ep["location"]):
                 cur_size = ek.ek(os.path.getsize, cur_ep["location"])
@@ -463,19 +467,19 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode):
 
         # check each snatch to see if we can use it to get a release name from
         history_results = self.connection.select("SELECT * FROM history WHERE provider != -1 ORDER BY date ASC")
-        
+
         logger.log(u"Adding release name to all episodes still in history")
         for cur_result in history_results:
             # find the associated download, if there isn't one then ignore it
             download_results = self.connection.select("SELECT resource FROM history WHERE provider = -1 AND showid = ? AND season = ? AND episode = ? AND date > ?",
                                                     [cur_result["showid"], cur_result["season"], cur_result["episode"], cur_result["date"]])
             if not download_results:
-                logger.log(u"Found a snatch in the history for "+cur_result["resource"]+" but couldn't find the associated download, skipping it", logger.DEBUG)
+                logger.log(u"Found a snatch in the history for " + cur_result["resource"] + " but couldn't find the associated download, skipping it", logger.DEBUG)
                 continue
 
             nzb_name = cur_result["resource"]
             file_name = ek.ek(os.path.basename, download_results[0]["resource"])
-            
+
             # take the extension off the filename, it's not needed
             if '.' in file_name:
                 file_name = file_name.rpartition('.')[0]
@@ -484,20 +488,20 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode):
             ep_results = self.connection.select("SELECT episode_id, status FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ? AND location != ''",
                                                 [cur_result["showid"], cur_result["season"], cur_result["episode"]])
             if not ep_results:
-                logger.log(u"The episode "+nzb_name+" was found in history but doesn't exist on disk anymore, skipping", logger.DEBUG)
+                logger.log(u"The episode " + nzb_name + " was found in history but doesn't exist on disk anymore, skipping", logger.DEBUG)
                 continue
 
-            # get the status/quality of the existing ep and make sure it's what we expect 
+            # get the status/quality of the existing ep and make sure it's what we expect
             ep_status, ep_quality = common.Quality.splitCompositeStatus(int(ep_results[0]["status"]))
             if ep_status != common.DOWNLOADED:
                 continue
-            
+
             if ep_quality != int(cur_result["quality"]):
-                continue 
+                continue
 
             # make sure this is actually a real release name and not a season pack or something
             for cur_name in (nzb_name, file_name):
-                logger.log(u"Checking if "+cur_name+" is actually a good release name", logger.DEBUG)
+                logger.log(u"Checking if " + cur_name + " is actually a good release name", logger.DEBUG)
                 try:
                     np = NameParser(False)
                     parse_result = np.parse(cur_name)
@@ -511,44 +515,43 @@ class AddSizeAndSceneNameFields(AddShowLangsToEpisode):
 
         # check each snatch to see if we can use it to get a release name from
         empty_results = self.connection.select("SELECT episode_id, location FROM tv_episodes WHERE release_name = ''")
-        
+
         logger.log(u"Adding release name to all episodes with obvious scene filenames")
         for cur_result in empty_results:
-            
+
             ep_file_name = ek.ek(os.path.basename, cur_result["location"])
             ep_file_name = os.path.splitext(ep_file_name)[0]
-            
+
             # I only want to find real scene names here so anything with a space in it is out
             if ' ' in ep_file_name:
                 continue
-            
+
             try:
                 np = NameParser(False)
                 parse_result = np.parse(ep_file_name)
             except InvalidNameException:
                 continue
-        
+
             if not parse_result.release_group:
                 continue
-            
-            logger.log(u"Name "+ep_file_name+" gave release group of "+parse_result.release_group+", seems valid", logger.DEBUG)
+
+            logger.log(u"Name " + ep_file_name + " gave release group of " + parse_result.release_group + ", seems valid", logger.DEBUG)
             self.connection.action("UPDATE tv_episodes SET release_name = ? WHERE episode_id = ?", [ep_file_name, cur_result["episode_id"]])
 
         self.incDBVersion()
 
-class RenameSeasonFolders(AddSizeAndSceneNameFields):
 
+class RenameSeasonFolders(AddSizeAndSceneNameFields):
     def test(self):
         return self.checkDBVersion() >= 11
-    
+
     def execute(self):
-        
         # rename the column
         self.connection.action("ALTER TABLE tv_shows RENAME TO tmp_tv_shows")
         self.connection.action("CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, location TEXT, show_name TEXT, tvdb_id NUMERIC, network TEXT, genre TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, tvr_id NUMERIC, tvr_name TEXT, air_by_date NUMERIC, lang TEXT, custom_search_names TEXT, audio_lang TEXT)")
         sql = "INSERT INTO tv_shows(show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, flatten_folders, paused, startyear, tvr_id, tvr_name, air_by_date, lang, custom_search_names, audio_lang) SELECT show_id, location, show_name, tvdb_id, network, genre, runtime, quality, airs, status, seasonfolders, paused, startyear, tvr_id, tvr_name, air_by_date, lang, custom_search_names, audio_lang FROM tmp_tv_shows"
         self.connection.action(sql)
-        
+
         # flip the values to be opposite of what they were before
         self.connection.action("UPDATE tv_shows SET flatten_folders = 2 WHERE flatten_folders = 1")
         self.connection.action("UPDATE tv_shows SET flatten_folders = 1 WHERE flatten_folders = 0")
@@ -556,3 +559,112 @@ class RenameSeasonFolders(AddSizeAndSceneNameFields):
         self.connection.action("DROP TABLE tmp_tv_shows")
 
         self.incDBVersion()
+
+
+class Add1080pAndRawHDQualities(RenameSeasonFolders):
+    """Add support for 1080p related qualities along with RawHD
+
+    Quick overview of what the upgrade needs to do:
+
+           quality   | old  | new
+        --------------------------
+        hdwebdl      | 1<<3 | 1<<5
+        hdbluray     | 1<<4 | 1<<7
+        fullhdbluray | 1<<5 | 1<<8
+        --------------------------
+        rawhdtv      |      | 1<<3
+        fullhdtv     |      | 1<<4
+        fullhdwebdl  |      | 1<<6
+    """
+
+    def test(self):
+        return self.checkDBVersion() >= 12
+
+    def _update_status(self, old_status):
+        (status, quality) = common.Quality.splitCompositeStatus(old_status)
+        return common.Quality.compositeStatus(status, self._update_quality(quality))
+
+    def _update_quality(self, old_quality):
+        """Update bitwise flags to reflect new quality values
+
+        Check flag bits (clear old then set their new locations) starting
+        with the highest bits so we dont overwrite data we need later on
+        """
+
+        result = old_quality
+        # move fullhdbluray from 1<<5 to 1<<8 if set
+        if(result & (1<<5)):
+            result = result & ~(1<<5)
+            result = result | (1<<8)
+        # move hdbluray from 1<<4 to 1<<7 if set
+        if(result & (1<<4)):
+            result = result & ~(1<<4)
+            result = result | (1<<7)
+        # move hdwebdl from 1<<3 to 1<<5 if set
+        if(result & (1<<3)):
+            result = result & ~(1<<3)
+            result = result | (1<<5)
+
+        return result
+
+    def _update_composite_qualities(self, status):
+        """Unpack, Update, Return new quality values
+
+        Unpack the composite archive/initial values.
+        Update either qualities if needed.
+        Then return the new compsite quality value.
+        """
+
+        best = (status & (0xffff << 16)) >> 16
+        initial = status & (0xffff)
+
+        best = self._update_quality(best)
+        initial = self._update_quality(initial)
+
+        result = ((best << 16) | initial)
+        return result
+
+    def execute(self):
+        backupDatabase(self.checkDBVersion())
+
+        # update the default quality so we dont grab the wrong qualities after migration
+        sickbeard.QUALITY_DEFAULT = self._update_composite_qualities(sickbeard.QUALITY_DEFAULT)
+        sickbeard.save_config()
+
+        # upgrade previous HD to HD720p -- shift previous qualities to new placevalues
+        old_hd = common.Quality.combineQualities([common.Quality.HDTV, common.Quality.HDWEBDL >> 2, common.Quality.HDBLURAY >> 3], [])
+        new_hd = common.Quality.combineQualities([common.Quality.HDTV, common.Quality.HDWEBDL, common.Quality.HDBLURAY], [])
+
+        # update ANY -- shift existing qualities and add new 1080p qualities, note that rawHD was not added to the ANY template
+        old_any = common.Quality.combineQualities([common.Quality.SDTV, common.Quality.SDDVD, common.Quality.HDTV, common.Quality.HDWEBDL >> 2, common.Quality.HDBLURAY >> 3, common.Quality.UNKNOWN], [])
+        new_any = common.Quality.combineQualities([common.Quality.SDTV, common.Quality.SDDVD, common.Quality.HDTV, common.Quality.FULLHDTV, common.Quality.HDWEBDL, common.Quality.FULLHDWEBDL, common.Quality.HDBLURAY, common.Quality.FULLHDBLURAY, common.Quality.UNKNOWN], [])
+
+        # update qualities (including templates)
+        shows = self.connection.select("SELECT * FROM tv_shows")
+        for cur_show in shows:
+            if cur_show["quality"] == old_hd:
+                new_quality = new_hd
+            elif cur_show["quality"] == old_any:
+                new_quality = new_any
+            else:
+                new_quality = self._update_composite_qualities(cur_show["quality"])
+            self.connection.action("UPDATE tv_shows SET quality = ? WHERE tvdb_id = ?", [new_quality, cur_show["tvdb_id"]])
+
+        # update status that are are within the old hdwebdl (1<<3 which is 8) and better -- exclude unknown (1<<15 which is 32768)
+        episodes = self.connection.select("SELECT * FROM tv_episodes WHERE status/100 < 32768 AND status/100 >= 8")
+        for cur_episode in episodes:
+            self.connection.action("UPDATE tv_episodes SET status = ? WHERE episode_id = ?", [self._update_status(cur_episode["status"]), cur_episode["episode_id"]])
+
+        # 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
+        historyAction = self.connection.select("SELECT * FROM history WHERE action/100 < 32768 AND action/100 >= 8")
+        for cur_entry in historyAction:
+            self.connection.action("UPDATE history SET action = ? WHERE showid = ? AND date = ?", [self._update_status(cur_entry["action"]), cur_entry["showid"], cur_entry["date"]])
+
+        # update previous history so it shows the correct quality
+        historyQuality = self.connection.select("SELECT * FROM history WHERE quality < 32768 AND quality >= 8")
+        for cur_entry in historyQuality:
+            self.connection.action("UPDATE history SET quality = ? WHERE showid = ? AND date = ?", [self._update_quality(cur_entry["quality"]), cur_entry["showid"], cur_entry["date"]])
+
+        self.incDBVersion()
diff --git a/sickbeard/db.py b/sickbeard/db.py
index 2d00745b045ee88796905c5cc53b59c0cb886cef..3bb56fe453fb9651b4cb5aafec2a140ab25f2159 100644
--- a/sickbeard/db.py
+++ b/sickbeard/db.py
@@ -54,6 +54,13 @@ class DBConnection:
         else:
             self.connection.row_factory = sqlite3.Row
 
+    def checkDBVersion(self):
+        result = self.select("SELECT db_version FROM db_version")
+        if result:
+            return int(result[0]["db_version"])
+        else:
+            return 0
+
     def action(self, query, args=None):
 
         with db_lock:
@@ -183,11 +190,7 @@ class SchemaUpgrade (object):
         self.connection.action("UPDATE %s SET %s = ?" % (table, column), (default,))
 
     def checkDBVersion(self):
-        result = self.connection.select("SELECT db_version FROM db_version")
-        if result:
-            return int(result[0]["db_version"])
-        else:
-            return 0
+        return self.connection.checkDBVersion()
 
     def incDBVersion(self):
         curVersion = self.checkDBVersion()
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index cec047891433aec48b511d528d35fabcdc153e8e..15ba0c38c7c1662a7a8fa3e1404af4a3e6ab5c85 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -37,6 +37,7 @@ from sickbeard.common import USER_AGENT, mediaExtensions, XML_NSMAP
 
 from sickbeard import db
 from sickbeard import encodingKludge as ek
+from sickbeard import notifiers
 
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
@@ -198,6 +199,8 @@ def makeDir (dir):
     if not ek.ek(os.path.isdir, dir):
         try:
             ek.ek(os.makedirs, dir)
+            # do the library update for synoindex
+            notifiers.synoindex_notifier.addFolder(dir)
         except OSError:
             return False
     return True
@@ -463,6 +466,8 @@ def make_dirs(path):
                     ek.ek(os.mkdir, sofar)
                     # use normpath to remove end separator, otherwise checks permissions against itself
                     chmodAsParent(ek.ek(os.path.normpath, sofar))
+                    # do the library update for synoindex
+                    notifiers.synoindex_notifier.addFolder(sofar)
                 except (OSError, IOError), e:
                     logger.log(u"Failed creating " + sofar + " : " + ex(e), logger.ERROR)
                     return False
@@ -525,6 +530,8 @@ def delete_empty_folders(check_empty_dir, keep_dir=None):
                 logger.log(u"Deleting empty folder: " + check_empty_dir)
                 # need shutil.rmtree when ignore_items is really implemented
                 ek.ek(os.rmdir, check_empty_dir)
+                # do the library update for synoindex
+                notifiers.synoindex_notifier.deleteFolder(check_empty_dir)
             except (WindowsError, OSError), e:
                 logger.log(u"Unable to delete " + check_empty_dir + ": " + repr(e) + " / " + str(e), logger.WARNING)
                 break
diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py
index b11e5a315c1fdb3827f9b84ff055462713275766..becde5c1abe0e6bcd95c71a539faa2da8c937c51 100644
--- a/sickbeard/name_parser/parser.py
+++ b/sickbeard/name_parser/parser.py
@@ -189,7 +189,6 @@ class NameParser(object):
         if type(number) == int:
             return number
 
-
         # good lord I'm lazy
         if number.lower() == 'i': return 1
         if number.lower() == 'ii': return 2
diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py
index c78aaabfc6f83a8e8996f18d81e53eaf64e60b66..fb28b7ddcd52eb9d364ba90277f35b4b2a355ffa 100755
--- a/sickbeard/notifiers/__init__.py
+++ b/sickbeard/notifiers/__init__.py
@@ -21,6 +21,7 @@ import sickbeard
 import xbmc
 import plex
 import nmj
+import nmjv2
 import synoindex
 import pytivo
 
@@ -42,6 +43,7 @@ xbmc_notifier = xbmc.XBMCNotifier()
 plex_notifier = plex.PLEXNotifier()
 nmj_notifier = nmj.NMJNotifier()
 synoindex_notifier = synoindex.synoIndexNotifier()
+nmjv2_notifier = nmjv2.NMJv2Notifier()
 pytivo_notifier = pytivo.pyTivoNotifier()
 # devices
 growl_notifier = growl.GrowlNotifier()
@@ -60,6 +62,7 @@ notifiers = [
     xbmc_notifier,
     plex_notifier,
     nmj_notifier,
+    nmjv2_notifier,
     synoindex_notifier,
     pytivo_notifier,
     growl_notifier,
diff --git a/sickbeard/notifiers/xbmc.py b/sickbeard/notifiers/xbmc.py
index 05c63d106f8a72c53f78a2c6d68655e4fe45e237..63528c644244aacb4946cae8804d4d8908ba8461 100644
--- a/sickbeard/notifiers/xbmc.py
+++ b/sickbeard/notifiers/xbmc.py
@@ -42,12 +42,16 @@ except ImportError:
 
 class XBMCNotifier:
 
-    def _get_json_version(self, host, username, password):
+    sb_logo_url = 'http://www.sickbeard.com/xbmc-notify.png'
+
+    def _get_xbmc_version(self, host, username, password):
         """Returns XBMC JSON-RPC API version (odd # = dev, even # = stable)
 
         Sends a request to the XBMC 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
@@ -80,7 +84,14 @@ class XBMCNotifier:
         if result:
             return result["result"]["version"]
         else:
-            return False
+            # fallback to legacy HTTPAPI method
+            testCommand = {'command': 'Help'}
+            request = self._send_to_xbmc(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="Sick Beard", host=None, username=None, password=None, force=False):
         """Internal wrapper for the notify_snatch and notify_download functions
@@ -118,7 +129,7 @@ class XBMCNotifier:
         for curHost in [x.strip() for x in host.split(",")]:
             logger.log(u"Sending XBMC notification to '" + curHost + "' - " + message, logger.MESSAGE)
 
-            xbmcapi = self._get_json_version(curHost, username, password)
+            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)
@@ -128,12 +139,13 @@ class XBMCNotifier:
                         result += curHost + ':' + str(notifyResult)
                 else:
                     logger.log(u"Detected XBMC version >= 12, using XBMC JSON API", logger.DEBUG)
-                    command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"%s","message":"%s"},"id":1}' % (title.encode("utf-8"), message.encode("utf-8"))
+                    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)
                     if notifyResult:
                         result += curHost + ':' + notifyResult["result"].decode(sickbeard.SYS_ENCODING)
             else:
-                logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.DEBUG)
+                logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR)
+                result += curHost + ':False'
 
         return result
 
@@ -257,7 +269,7 @@ class XBMCNotifier:
                 return False
 
             for path in paths:
-                # Don't need it double-encoded, gawd this is dumb
+                # 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)}
@@ -402,7 +414,7 @@ class XBMCNotifier:
                 return False
 
             logger.log(u"XBMC Updating " + showName + " on " + host + " at " + path, logger.DEBUG)
-            updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (json.dumps(path))
+            updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (json.dumps("\"" + path + "\"")) # yes we really have to wrap the path in escaped "
             request = self._send_to_xbmc_json(updateCommand, host)
             if not request:
                 logger.log(u"Update of show directory failed on " + showName + " on " + host + " at " + path, logger.ERROR)
@@ -462,32 +474,37 @@ class XBMCNotifier:
                 logger.log(u"No XBMC hosts specified, check your settings", logger.DEBUG)
                 return False
 
-            # only send update to first host in the list -- workaround for xbmc sql backend users
-            host = sickbeard.XBMC_HOST.split(",")[0].strip()
-
-            logger.log(u"Sending request to update library for XBMC host: '" + host + "'", logger.MESSAGE)
-
-            xbmcapi = self._get_json_version(host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD)
-            if xbmcapi:
-                if (xbmcapi <= 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:
-                        logger.log(u"Single show update failed, falling back to full update", logger.WARNING)
-                        return self._update_library(host)
+            if sickbeard.XBMC_UPDATE_ONLYFIRST:
+                # only send update to first host in the list if requested -- workaround for xbmc sql backend users
+                host = sickbeard.XBMC_HOST.split(",")[0].strip()
+            else:
+                host = sickbeard.XBMC_HOST
+
+            result = 0
+            for curHost in [x.strip() for x in host.split(",")]:
+                logger.log(u"Sending request to update library for XBMC host: '" + curHost + "'", logger.MESSAGE)
+
+                xbmcapi = self._get_xbmc_version(curHost, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD)
+                if xbmcapi:
+                    if (xbmcapi <= 4):
+                        # try to update for just the show, if it fails, do full update if enabled
+                        if not self._update_library(curHost, showName) and sickbeard.XBMC_UPDATE_FULL:
+                            logger.log(u"Single show update failed, falling back to full update", logger.WARNING)
+                            self._update_library(curHost)
                     else:
-                        return True
+                        # try to update for just the show, if it fails, do full update if enabled
+                        if not self._update_library_json(curHost, showName) and sickbeard.XBMC_UPDATE_FULL:
+                            logger.log(u"Single show update failed, falling back to full update", logger.WARNING)
+                            self._update_library_json(curHost)
                 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:
-                        logger.log(u"Single show update failed, falling back to full update", logger.WARNING)
-                        return self._update_library_json(host)
-                    else:
-                        return True
+                    logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR)
+                    result = result + 1
+
+            # needed for the 'update xbmc' submenu command
+            # as it only cares of the final result vs the individual ones
+            if result == 0:
+                return True
             else:
-                logger.log(u"Failed to detect XBMC version for '" + host + "', check configuration and try again.", logger.DEBUG)
                 return False
 
-            return True
-
-
 notifier = XBMCNotifier
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index 00cbaad4270d0c85dfd207adaa076fbb83fbd1f7..d73bd2c0d37128691df2816baa6ddcb292811a81 100755
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -700,13 +700,13 @@ class PostProcessor(object):
 
         self._log(u"Processing " + self.file_path + " (" + str(self.nzb_name) + ")")
 
-        if os.path.isdir(self.file_path):
-            self._log(u"File " + self.file_path + " seems to be a directory")
-            return False
-        for ignore_file in self.IGNORED_FILESTRINGS:
-            if ignore_file in self.file_path:
-                self._log(u"File " + self.file_path + " is ignored type, skipping")
-                return False
+        if os.path.isdir(self.file_path):
+            self._log(u"File " + self.file_path + " seems to be a directory")
+            return False
+        for ignore_file in self.IGNORED_FILESTRINGS:
+            if ignore_file in self.file_path:
+                self._log(u"File " + self.file_path + " is ignored type, skipping")
+                return False
         # reset per-file stuff
         self.in_history = False
 
@@ -767,6 +767,8 @@ class PostProcessor(object):
             self._log(u"Show directory doesn't exist, creating it", logger.DEBUG)
             try:
                 ek.ek(os.mkdir, ep_obj.show._location)
+                # do the library update for synoindex
+                notifiers.synoindex_notifier.addFolder(ep_obj.show._location)
 
             except (OSError, IOError):
                 raise exceptions.PostProcessingFailed("Unable to create the show directory: " + ep_obj.show._location)
@@ -852,10 +854,10 @@ class PostProcessor(object):
         ep_obj.createMetaFiles()
         ep_obj.saveToDB()
 
-        # do the library update for XBMC
+        # do the library update for XBMC
         notifiers.xbmc_notifier.update_library(ep_obj.show.name)
 
-        # do the library update for Plex
+        # do the library update for Plex
         notifiers.plex_notifier.update_library()
 
         # do the library update for NMJ
@@ -866,8 +868,8 @@ class PostProcessor(object):
 
         # do the library update for pyTivo
         notifiers.pytivo_notifier.update_library(ep_obj)
-
-        # do the library update for Trakt
+
+        # do the library update for Trakt
         notifiers.trakt_notifier.update_library(ep_obj)
 
         self._run_extra_scripts(ep_obj)
diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py
index 57c41569d90da55df1320343479633feaaa47267..267f8ed576cf43c9cf313f406e7811e327896898 100644
--- a/sickbeard/processTV.py
+++ b/sickbeard/processTV.py
@@ -94,6 +94,10 @@ def processDir (dirName, nzbName=None, recurse=False):
 
     remainingFolders = filter(lambda x: ek.ek(os.path.isdir, ek.ek(os.path.join, dirName, x)), fileList)
 
+    # If nzbName is set and there's more than one videofile in the folder, files will be lost (overwritten).
+    if nzbName != None and len(videoFiles) >= 2:
+        nzbName = None
+
     # process any files in the dir
     for cur_video_file_path in videoFiles:
 
diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py
index 867501d52871bc748b87525395766806bd086911..7300cf488d391e391a0df9c97e27abf020404dc7 100755
--- a/sickbeard/providers/__init__.py
+++ b/sickbeard/providers/__init__.py
@@ -18,9 +18,12 @@
 
 __all__ = ['ezrss',
            'tvtorrents',
+           'torrentleech',
            'nzbsrus',
            'womble',
            'btn',
+           'nzbx',
+           'omgwtfnzbs',
            'binnewz',
            't411'
            ]
@@ -29,6 +32,7 @@ import sickbeard
 
 from os import sys
 
+
 def sortedProviderList():
 
     initialList = sickbeard.providerList + sickbeard.newznabProviderList
@@ -48,10 +52,12 @@ def sortedProviderList():
 
     return newList
 
+
 def makeProviderList():
 
     return [x.provider for x in [getProviderModule(y) for y in __all__] if x]
 
+
 def getNewznabProviderList(data):
 
     defaultList = [makeNewznabProvider(x) for x in getDefaultNewznabProviders().split('!!!')]
@@ -76,7 +82,7 @@ def getNewznabProviderList(data):
             providerDict[curDefault.name].name = curDefault.name
             providerDict[curDefault.name].url = curDefault.url
             providerDict[curDefault.name].needs_auth = curDefault.needs_auth
-        
+
     return filter(lambda x: x, providerList)
 
 
@@ -95,21 +101,23 @@ def makeNewznabProvider(configString):
 
     return newProvider
 
+
 def getDefaultNewznabProviders():
-    return 'Sick Beard Index|http://lolo.sickbeard.com/|0|0!!!NZBs.org|http://beta.nzbs.org/||0!!!NZBGeek|https://index.nzbgeek.info/||0!!!NZBFinder|http://www.nzbfinder.ws/||0!!!Usenet-Crawler|http://www.usenet-crawler.com/||0'
+    return 'Sick Beard Index|http://lolo.sickbeard.com/|0|0!!!NZBs.org|http://nzbs.org/||0!!!NZBGeek|https://index.nzbgeek.info/||0!!!NZBFinder|http://www.nzbfinder.ws/||0!!!Usenet-Crawler|http://www.usenet-crawler.com/||0'
 
 
 def getProviderModule(name):
     name = name.lower()
     prefix = "sickbeard.providers."
-    if name in __all__ and prefix+name in sys.modules:
-        return sys.modules[prefix+name]
+    if name in __all__ and prefix + name in sys.modules:
+        return sys.modules[prefix + name]
     else:
-        raise Exception("Can't find "+prefix+name+" in "+repr(sys.modules))
+        raise Exception("Can't find " + prefix + name + " in " + repr(sys.modules))
+
 
-def getProviderClass(id):
+def getProviderClass(providerID):
 
-    providerMatch = [x for x in sickbeard.providerList+sickbeard.newznabProviderList if x.getID() == id]
+    providerMatch = [x for x in sickbeard.providerList + sickbeard.newznabProviderList if x.getID() == providerID]
 
     if len(providerMatch) != 1:
         return None
diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py
index fe3d1b4b2430cb9342b445e93b021b6ab6a699ba..aeddbb215ca181f8cacce5db63d61f7940fb96a1 100644
--- a/sickbeard/providers/btn.py
+++ b/sickbeard/providers/btn.py
@@ -1,5 +1,5 @@
 # coding=utf-8
-# Author: Dani�l Heimans
+# Author: Dani�l Heimans
 # URL: http://code.google.com/p/sickbeard
 #
 # This file is part of Sick Beard.
@@ -59,7 +59,7 @@ class BTNProvider(generic.TorrentProvider):
 
         return result
 
-    def _doSearch(self, search_params, show=None):
+    def _doSearch(self, search_params, show=None, season=None):
         params = {}
         apikey = sickbeard.BTN_API_KEY
 
diff --git a/sickbeard/providers/ezrss.py b/sickbeard/providers/ezrss.py
index ca8cd72b3c912e092155e487b85a2a811cbe7cca..9900d777b4c1e3305006b70aee45405a5c5c6934 100644
--- a/sickbeard/providers/ezrss.py
+++ b/sickbeard/providers/ezrss.py
@@ -99,7 +99,7 @@ class EZRSSProvider(generic.TorrentProvider):
     
         return [params]
 
-    def _doSearch(self, search_params, show=None):
+    def _doSearch(self, search_params, show=None, season= None):
     
         params = {"mode": "rss"}
     
diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py
index b2ca02a836cbc6dcedd9af390d8324a54efae733..4848c976a7dae844e045107a59cc633b4eca7457 100644
--- a/sickbeard/providers/generic.py
+++ b/sickbeard/providers/generic.py
@@ -107,8 +107,6 @@ class GenericProvider:
         if not headers:
             headers = []
 
-
-
         result = helpers.getURL(url, headers)
 
         if result is None:
diff --git a/sickbeard/providers/newznab.py b/sickbeard/providers/newznab.py
index b1e07e770f494cb7a9c4a39eec6a7bb457785223..d0735bd75f471be6c3ce01cf723c0383e8d6aa75 100644
--- a/sickbeard/providers/newznab.py
+++ b/sickbeard/providers/newznab.py
@@ -318,7 +318,6 @@ class NewznabCache(tvcache.TVCache):
             cat = '5020'
 
         params = {"t": "tvsearch",
-                  "age": sickbeard.USENET_RETENTION,
                   "cat": cat}
 
         # hack this in for now
diff --git a/sickbeard/providers/nzbsrus.py b/sickbeard/providers/nzbsrus.py
index c90365f30768bd1d576459f6f440f772664ea85c..9d140c5686fc62c09f106b7855316c38e965c3e4 100644
--- a/sickbeard/providers/nzbsrus.py
+++ b/sickbeard/providers/nzbsrus.py
@@ -16,62 +16,107 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-
-
 import urllib
-
+import generic
 import sickbeard
 
-from sickbeard import exceptions, logger
+try:
+    import xml.etree.cElementTree as etree
+except ImportError:
+    import xml.etree.ElementTree as etree
 
-from sickbeard import tvcache
+from sickbeard import exceptions, logger
+from sickbeard import tvcache, show_name_helpers
 
-import generic
 
 class NZBsRUSProvider(generic.NZBProvider):
 
-	def __init__(self):
-
-		generic.NZBProvider.__init__(self, "NZBs'R'US")
-
-		self.cache = NZBsRUSCache(self)
-
-		self.url = 'https://www.nzbsrus.com/'
-
-	def isEnabled(self):
-		return sickbeard.NZBSRUS
-
-	def _checkAuth(self):
-		if sickbeard.NZBSRUS_UID in (None, "") or sickbeard.NZBSRUS_HASH in (None, ""):
-			raise exceptions.AuthException("NZBs'R'US authentication details are empty, check your config")
+    def __init__(self):
+        generic.NZBProvider.__init__(self, "NZBs'R'US")
+        self.cache = NZBsRUSCache(self)
+        self.url = 'https://www.nzbsrus.com/'
+        self.supportsBacklog = True
+
+    def isEnabled(self):
+        return sickbeard.NZBSRUS
+
+    def _checkAuth(self):
+        if sickbeard.NZBSRUS_UID in (None, "") or sickbeard.NZBSRUS_HASH in (None, ""):
+            raise exceptions.AuthException("NZBs'R'US authentication details are empty, check your config")
+
+    def _get_season_search_strings(self, show, season):
+        return [x for x in show_name_helpers.makeSceneSeasonSearchString(show, season)]
+
+    def _get_episode_search_strings(self, ep_obj):
+        return [x for x in show_name_helpers.makeSceneSearchString(ep_obj)]
+
+    def _doSearch(self, search, show=None, season=None):
+        params = {'uid': sickbeard.NZBSRUS_UID,
+                  'key': sickbeard.NZBSRUS_HASH,
+                  'xml': 1,
+                  'age': sickbeard.USENET_RETENTION,
+                  'lang0': 1,   # English only from CouchPotato
+                  'lang1': 1,
+                  'lang3': 1,
+                  'c91': 1,     # TV:HD
+                  'c104': 1,    # TV:SD-x264
+                  'c75': 1,     # TV:XviD
+                  'searchtext': search}
+
+        if not params['age']:
+            params['age'] = 500
+
+        searchURL = self.url + 'api.php?' + urllib.urlencode(params)
+        logger.log(u"NZBS'R'US search url: " + searchURL, logger.DEBUG)
+
+        data = self.getURL(searchURL)
+        if not data:
+            return []
+
+        if not data.startswith('<?xml'):  # Error will be a single line of text
+            logger.log(u"NZBs'R'US error: " + data, logger.ERROR)
+            return []
+
+        root = etree.fromstring(data)
+        if root is None:
+            logger.log(u"Error trying to parse NZBS'R'US XML data.", logger.ERROR)
+            logger.log(u"RSS data: " + data, logger.DEBUG)
+            return []
+        return root.findall('./results/result')
+
+    def _get_title_and_url(self, element):
+        if element.find('title'):  # RSS feed
+            title = element.find('title').text
+            url = element.find('link').text.replace('&amp;', '&')
+        else:  # API item
+            title = element.find('name').text
+            nzbID = element.find('id').text
+            key = element.find('key').text
+            url = self.url + 'nzbdownload_rss.php' + '/' + \
+                nzbID + '/' + sickbeard.NZBSRUS_UID + '/' + key + '/'
+        return (title, url)
 
 
 class NZBsRUSCache(tvcache.TVCache):
 
-	def __init__(self, provider):
-
-		tvcache.TVCache.__init__(self, provider)
-
-		# only poll NZBs'R'US every 15 minutes max
-		self.minTime = 15
-
-
-	def _getRSSData(self):
-
-		url = self.provider.url + 'rssfeed.php?'
-		urlArgs = {'cat': '91,75',
-				   'i': sickbeard.NZBSRUS_UID,
-				   'h': sickbeard.NZBSRUS_HASH}
-
-		url += urllib.urlencode(urlArgs)
+    def __init__(self, provider):
+        tvcache.TVCache.__init__(self, provider)
+        # only poll NZBs'R'US every 15 minutes max
+        self.minTime = 15
 
-		logger.log(u"NZBs'R'US cache update URL: "+ url, logger.DEBUG)
+    def _getRSSData(self):
+        url = self.provider.url + 'rssfeed.php?'
+        urlArgs = {'cat': '91,75,104',  # HD,XviD,SD-x264
+                   'i': sickbeard.NZBSRUS_UID,
+                   'h': sickbeard.NZBSRUS_HASH}
 
-		data = self.provider.getURL(url)
+        url += urllib.urlencode(urlArgs)
+        logger.log(u"NZBs'R'US cache update URL: " + url, logger.DEBUG)
 
-		return data
+        data = self.provider.getURL(url)
+        return data
 
-	def _checkAuth(self, data):
-		return data != 'Invalid Link'
+    def _checkAuth(self, data):
+        return data != 'Invalid Link'
 
-provider = NZBsRUSProvider()
\ No newline at end of file
+provider = NZBsRUSProvider()
diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py
index 059c5f256d5a2774c6f8f9eaa5f9aec5859fd8d5..1a7a7aac39eda99204061f97f941546402c63ad8 100644
--- a/sickbeard/show_name_helpers.py
+++ b/sickbeard/show_name_helpers.py
@@ -174,6 +174,8 @@ def makeSceneSearchString (episode):
     myDB = db.DBConnection()
     numseasonsSQlResult = myDB.select("SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0", [episode.show.tvdbid])
     numseasons = int(numseasonsSQlResult[0][0])
+    numepisodesSQlResult = myDB.select("SELECT COUNT(episode) as numepisodes FROM tv_episodes WHERE showid = ? and season != 0", [episode.show.tvdbid])
+    numepisodes = int(numepisodesSQlResult[0][0])
 
     # see if we should use dates instead of episodes
     if episode.show.air_by_date and episode.airdate != datetime.date.fromordinal(1):
@@ -182,8 +184,9 @@ def makeSceneSearchString (episode):
         epStrings = ["S%02iE%02i" % (int(episode.season), int(episode.episode)),
                     "%ix%02i" % (int(episode.season), int(episode.episode))]
 
-    # for single-season shows just search for the show name
-    if numseasons == 1:
+    # for single-season shows just search for the show name -- if total ep count (exclude s0) is less than 11
+    # due to the amount of qualities and releases, it is easy to go over the 50 result limit on rss feeds otherwise
+    if numseasons == 1 and numepisodes < 11:
         epStrings = ['']
 
     showNames = set(makeSceneShowSearchStrings(episode.show))
diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py
index 97f3960635949eecca7e3718cdadffba15029c61..6cc667e5ed03cfa824b4729b2dbb69daffe68960 100644
--- a/sickbeard/show_queue.py
+++ b/sickbeard/show_queue.py
@@ -32,13 +32,13 @@ from sickbeard import generic_queue
 from sickbeard import name_cache
 from sickbeard.exceptions import ex
 
+
 class ShowQueue(generic_queue.GenericQueue):
 
     def __init__(self):
         generic_queue.GenericQueue.__init__(self)
         self.queue_name = "SHOWQUEUE"
 
-
     def _isInQueue(self, show, actions):
         return show in [x.show for x in self.queue if x.action_id in actions]
 
@@ -68,7 +68,7 @@ class ShowQueue(generic_queue.GenericQueue):
         return self._isBeingSomethinged(show, (ShowQueueActions.RENAME,))
 
     def _getLoadingShowList(self):
-        return [x for x in self.queue+[self.currentItem] if x != None and x.isLoading]
+        return [x for x in self.queue + [self.currentItem] if x != None and x.isLoading]
 
     loadingShowList = property(_getLoadingShowList)
 
@@ -102,7 +102,7 @@ class ShowQueue(generic_queue.GenericQueue):
             return
 
         queueItemObj = QueueItemRefresh(show)
-        
+
         self.add_item(queueItemObj)
 
         return queueItemObj
@@ -122,13 +122,14 @@ class ShowQueue(generic_queue.GenericQueue):
 
         return queueItemObj
 
+
 class ShowQueueActions:
-    REFRESH=1
-    ADD=2
-    UPDATE=3
-    FORCEUPDATE=4
-    RENAME=5
-    
+    REFRESH = 1
+    ADD = 2
+    UPDATE = 3
+    FORCEUPDATE = 4
+    RENAME = 5
+
     names = {REFRESH: 'Refresh',
                     ADD: 'Add',
                     UPDATE: 'Update',
@@ -136,6 +137,7 @@ class ShowQueueActions:
                     RENAME: 'Rename',
                     }
 
+
 class ShowQueueItem(generic_queue.QueueItem):
     """
     Represents an item in the queue waiting to be executed
@@ -149,9 +151,9 @@ class ShowQueueItem(generic_queue.QueueItem):
     def __init__(self, action_id, show):
         generic_queue.QueueItem.__init__(self, ShowQueueActions.names[action_id], action_id)
         self.show = show
-    
+
     def isInQueue(self):
-        return self in sickbeard.showQueueScheduler.action.queue+[sickbeard.showQueueScheduler.action.currentItem] #@UndefinedVariable
+        return self in sickbeard.showQueueScheduler.action.queue + [sickbeard.showQueueScheduler.action.currentItem] #@UndefinedVariable
 
     def _getName(self):
         return str(self.show.tvdbid)
@@ -179,7 +181,7 @@ class QueueItemAdd(ShowQueueItem):
 
         # this will initialize self.show to None
         ShowQueueItem.__init__(self, ShowQueueActions.ADD, self.show)
-        
+
     def _getName(self):
         """
         Returns the show name if there is a show object created, if not returns
@@ -206,7 +208,7 @@ class QueueItemAdd(ShowQueueItem):
 
         ShowQueueItem.execute(self)
 
-        logger.log(u"Starting to add show "+self.showDir)
+        logger.log(u"Starting to add show " + self.showDir)
 
         try:
             # make sure the tvdb ids are valid
@@ -214,9 +216,9 @@ class QueueItemAdd(ShowQueueItem):
                 ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy()
                 if self.lang:
                     ltvdb_api_parms['language'] = self.lang
-        
-                logger.log(u"TVDB: "+repr(ltvdb_api_parms))
-        
+
+                logger.log(u"TVDB: " + repr(ltvdb_api_parms))
+
                 t = tvdb_api.Tvdb(**ltvdb_api_parms)
                 s = t[self.tvdb_id]
 
@@ -233,8 +235,8 @@ class QueueItemAdd(ShowQueueItem):
                     self._finishEarly()
                     return
             except tvdb_exceptions.tvdb_exception, e:
-                logger.log(u"Error contacting TVDB: "+ex(e), logger.ERROR)
-                ui.notifications.error("Unable to add show", "Unable to look up the show in "+self.showDir+" on TVDB, not using the NFO. Delete .nfo and add manually in the correct language.")
+                logger.log(u"Error contacting TVDB: " + ex(e), logger.ERROR)
+                ui.notifications.error("Unable to add show", "Unable to look up the show in " + self.showDir + " on TVDB, not using the NFO. Delete .nfo and add manually in the correct language.")
                 self._finishEarly()
                 return
 
@@ -250,16 +252,16 @@ class QueueItemAdd(ShowQueueItem):
             self.show.location = self.showDir
             self.show.quality = self.quality if self.quality else sickbeard.QUALITY_DEFAULT
             self.show.flatten_folders = self.flatten_folders if self.flatten_folders != None else sickbeard.FLATTEN_FOLDERS_DEFAULT
-            self.show.paused = False
-            
+            self.show.paused = 0
+
             # be smartish about this
             if self.show.genre and "talk show" in self.show.genre.lower():
                 self.show.air_by_date = 1
 
         except tvdb_exceptions.tvdb_exception, e:
-            logger.log(u"Unable to add show due to an error with TVDB: "+ex(e), logger.ERROR)
+            logger.log(u"Unable to add show due to an error with TVDB: " + ex(e), logger.ERROR)
             if self.show:
-                ui.notifications.error("Unable to add "+str(self.show.name)+" due to an error with TVDB")
+                ui.notifications.error("Unable to add " + str(self.show.name) + " due to an error with TVDB")
             else:
                 ui.notifications.error("Unable to add show due to an error with TVDB")
             self._finishEarly()
@@ -272,7 +274,7 @@ class QueueItemAdd(ShowQueueItem):
             return
 
         except Exception, e:
-            logger.log(u"Error trying to add show: "+ex(e), logger.ERROR)
+            logger.log(u"Error trying to add show: " + ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
             self._finishEarly()
             raise
@@ -292,7 +294,7 @@ class QueueItemAdd(ShowQueueItem):
 
             self.show.writeMetadata()
             self.show.populateCache()
-            
+
         except Exception, e:
             logger.log(u"Error with TVDB, not creating episode list: " + ex(e), logger.ERROR)
             logger.log(traceback.format_exc(), logger.DEBUG)
@@ -305,8 +307,8 @@ class QueueItemAdd(ShowQueueItem):
 
         # if they gave a custom status then change all the eps to it
         if self.default_status != SKIPPED:
-            logger.log(u"Setting all episodes to the specified default status: "+str(self.default_status))
-            myDB = db.DBConnection();
+            logger.log(u"Setting all episodes to the specified default status: " + str(self.default_status))
+            myDB = db.DBConnection()
             myDB.action("UPDATE tv_episodes SET status = ? WHERE status = ? AND showid = ? AND season != 0", [self.default_status, SKIPPED, self.show.tvdbid])
 
         # if they started with WANTED eps then run the backlog
@@ -336,7 +338,7 @@ class QueueItemRefresh(ShowQueueItem):
 
         ShowQueueItem.execute(self)
 
-        logger.log(u"Performing refresh on "+self.show.name)
+        logger.log(u"Performing refresh on " + self.show.name)
 
         self.show.refreshDir()
         self.show.writeMetadata()
@@ -395,13 +397,13 @@ class QueueItemUpdate(ShowQueueItem):
 
         ShowQueueItem.execute(self)
 
-        logger.log(u"Beginning update of "+self.show.name)
+        logger.log(u"Beginning update of " + self.show.name)
 
         logger.log(u"Retrieving show info from TVDB", logger.DEBUG)
         try:
             self.show.loadFromTVDB(cache=not self.force)
         except tvdb_exceptions.tvdb_error, e:
-            logger.log(u"Unable to contact TVDB, aborting: "+ex(e), logger.WARNING)
+            logger.log(u"Unable to contact TVDB, aborting: " + ex(e), logger.WARNING)
             return
 
         # get episode list from DB
@@ -413,7 +415,7 @@ class QueueItemUpdate(ShowQueueItem):
         try:
             TVDBEpList = self.show.loadEpisodesFromTVDB(cache=not self.force)
         except tvdb_exceptions.tvdb_exception, e:
-            logger.log(u"Unable to get info from TVDB, the show info will not be refreshed: "+ex(e), logger.ERROR)
+            logger.log(u"Unable to get info from TVDB, the show info will not be refreshed: " + ex(e), logger.ERROR)
             TVDBEpList = None
 
         if TVDBEpList == None:
@@ -424,14 +426,14 @@ class QueueItemUpdate(ShowQueueItem):
             # for each ep we found on TVDB delete it from the DB list
             for curSeason in TVDBEpList:
                 for curEpisode in TVDBEpList[curSeason]:
-                    logger.log(u"Removing "+str(curSeason)+"x"+str(curEpisode)+" from the DB list", logger.DEBUG)
+                    logger.log(u"Removing " + str(curSeason) + "x" + str(curEpisode) + " from the DB list", logger.DEBUG)
                     if curSeason in DBEpList and curEpisode in DBEpList[curSeason]:
                         del DBEpList[curSeason][curEpisode]
 
             # for the remaining episodes in the DB list just delete them from the DB
             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)
+                    logger.log(u"Permanently deleting episode " + str(curSeason) + "x" + str(curEpisode) + " from the database", logger.MESSAGE)
                     curEp = self.show.getEpisode(curSeason, curEpisode)
                     try:
                         curEp.deleteEpisode()
@@ -447,6 +449,7 @@ class QueueItemUpdate(ShowQueueItem):
 
         sickbeard.showQueueScheduler.action.refreshShow(self.show, True) #@UndefinedVariable
 
+
 class QueueItemForceUpdate(QueueItemUpdate):
     def __init__(self, show=None):
         ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show)
diff --git a/sickbeard/tv.py b/sickbeard/tv.py
index bbacf27c74801c95d25a0f35039017d750aea498..892ea95e4f7e35180d1f101240c65dd865b7ab56 100644
--- a/sickbeard/tv.py
+++ b/sickbeard/tv.py
@@ -927,8 +927,10 @@ class TVShow(object):
 
             epStatus, curQuality = Quality.splitCompositeStatus(epStatus)
 
+            if epStatus in (SNATCHED, SNATCHED_PROPER):
+                return Overview.SNATCHED
             # if they don't want re-downloads then we call it good if they have anything
-            if maxBestQuality == None:
+            elif maxBestQuality == None:
                 return Overview.GOOD
             # if they have one but it's not the best they want then mark it as qual
             elif curQuality < maxBestQuality:
@@ -960,6 +962,7 @@ class TVEpisode(object):
         self._file_size = 0
         self._audio_langs = ''
         self._release_name = ''
+
         # setting any of the above sets the dirty flag
         self.dirty = True
 
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index afa7cee3be8a7d6d9f1415b07040dc04fe79e40f..daa90785d053ee85c61d23838f5963180659ac25 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -459,7 +459,7 @@ class SourceUpdateManager(GitUpdateManager):
             logger.log(u"Unable to retrieve new version from "+tar_download_url+", can't update", logger.ERROR)
             return False
 
-        download_name = data.geturl().split('/')[-1]
+        download_name = data.geturl().split('/')[-1].split('?')[0]
 
         tar_download_path = os.path.join(sickbeard.PROG_DIR, download_name)
 
diff --git a/sickbeard/webapi.py b/sickbeard/webapi.py
index 691730da9dd8e36cf2562da6024fe4adf70bb754..091a51b4c17267ca08b98cf4d37222c2ec5cf69d 100644
--- a/sickbeard/webapi.py
+++ b/sickbeard/webapi.py
@@ -35,7 +35,7 @@ from sickbeard.exceptions import ex
 from sickbeard import encodingKludge as ek
 from sickbeard import search_queue
 from sickbeard.common import SNATCHED, SNATCHED_PROPER, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED, UNKNOWN
-from common import ANY, Quality, qualityPresetStrings, statusStrings
+from common import Quality, qualityPresetStrings, statusStrings
 from sickbeard import image_cache
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 try:
@@ -67,7 +67,7 @@ result_type_map = {RESULT_SUCCESS: "success",
 
 class Api:
     """ api class that returns json results """
-    version = 3 # use an int since float-point is unpredictible
+    version = 4 # use an int since float-point is unpredictible
     intent = 4
 
     @cherrypy.expose
@@ -605,11 +605,13 @@ def _getQualityMap():
     return {Quality.SDTV: 'sdtv',
             Quality.SDDVD: 'sddvd',
             Quality.HDTV: 'hdtv',
+            Quality.RAWHDTV: 'rawhdtv',
+            Quality.FULLHDTV: 'fullhdtv',
             Quality.HDWEBDL: 'hdwebdl',
+            Quality.FULLHDWEBDL: 'fullhdwebdl',
             Quality.HDBLURAY: 'hdbluray',
             Quality.FULLHDBLURAY: 'fullhdbluray',
-            Quality.UNKNOWN: 'unknown',
-            ANY: 'any'}
+            Quality.UNKNOWN: 'unknown'}
 
 
 def _getRootDirs():
@@ -1533,8 +1535,8 @@ class CMD_SickBeardSetDefaults(ApiCall):
     def __init__(self, args, kwargs):
         # required
         # optional
-        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
-        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
+        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
+        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"])
         self.future_show_paused, args = self.check_params(args, kwargs, "future_show_paused", None, False, "bool", [])
         self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", None, False, "bool", [])
         self.status, args = self.check_params(args, kwargs, "status", None, False, "string", ["wanted", "skipped", "archived", "ignored"])
@@ -1547,11 +1549,13 @@ class CMD_SickBeardSetDefaults(ApiCall):
         quality_map = {'sdtv': Quality.SDTV,
                        'sddvd': Quality.SDDVD,
                        'hdtv': Quality.HDTV,
+                       'rawhdtv': Quality.RAWHDTV,
+                       'fullhdtv': Quality.FULLHDTV,
                        'hdwebdl': Quality.HDWEBDL,
+                       'fullhdwebdl': Quality.FULLHDWEBDL,
                        'hdbluray': Quality.HDBLURAY,
                        'fullhdbluray': Quality.FULLHDBLURAY,
-                       'unknown': Quality.UNKNOWN,
-                       'any': ANY }
+                       'unknown': Quality.UNKNOWN}
 
         iqualityID = []
         aqualityID = []
@@ -1683,8 +1687,8 @@ class CMD_ShowAddExisting(ApiCall):
         self.location, args = self.check_params(args, kwargs, "location", None, True, "string", [])
         self.tvdbid, args = self.check_params(args, kwargs, "tvdbid", None, True, "int", [])
         # optional
-        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
-        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
+        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
+        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"])
         self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", [])
         # super, missing, help
         ApiCall.__init__(self, args, kwargs)
@@ -1713,11 +1717,13 @@ class CMD_ShowAddExisting(ApiCall):
         quality_map = {'sdtv': Quality.SDTV,
                        'sddvd': Quality.SDDVD,
                        'hdtv': Quality.HDTV,
+                       'rawhdtv': Quality.RAWHDTV,
+                       'fullhdtv': Quality.FULLHDTV,
                        'hdwebdl': Quality.HDWEBDL,
+                       'fullhdwebdl': Quality.FULLHDWEBDL,
                        'hdbluray': Quality.HDBLURAY,
                        'fullhdbluray': Quality.FULLHDBLURAY,
-                       'unknown': Quality.UNKNOWN,
-                       'any': ANY }
+                       'unknown': Quality.UNKNOWN}
 
         #use default quality as a failsafe
         newQuality = int(sickbeard.QUALITY_DEFAULT)
@@ -1762,8 +1768,8 @@ class CMD_ShowAddNew(ApiCall):
         self.tvdbid, args = self.check_params(args, kwargs, "tvdbid", None, True, "int", [])
         # optional
         self.location, args = self.check_params(args, kwargs, "location", None, False, "string", [])
-        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
-        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
+        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
+        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"])
         self.flatten_folders, args = self.check_params(args, kwargs, "flatten_folders", str(sickbeard.FLATTEN_FOLDERS_DEFAULT), False, "bool", [])
         self.status, args = self.check_params(args, kwargs, "status", None, False, "string", ["wanted", "skipped", "archived", "ignored"])
         self.lang, args = self.check_params(args, kwargs, "lang", "en", False, "string", self.valid_languages.keys())
@@ -1791,11 +1797,13 @@ class CMD_ShowAddNew(ApiCall):
         quality_map = {'sdtv': Quality.SDTV,
                        'sddvd': Quality.SDDVD,
                        'hdtv': Quality.HDTV,
+                       'rawhdtv': Quality.RAWHDTV,
+                       'fullhdtv': Quality.FULLHDTV,
                        'hdwebdl': Quality.HDWEBDL,
+                       'fullhdwebdl': Quality.FULLHDWEBDL,
                        'hdbluray': Quality.HDBLURAY,
                        'fullhdbluray': Quality.FULLHDBLURAY,
-                       'unknown': Quality.UNKNOWN,
-                       'any': ANY }
+                       'unknown': Quality.UNKNOWN}
 
         # use default quality as a failsafe
         newQuality = int(sickbeard.QUALITY_DEFAULT)
@@ -2148,49 +2156,29 @@ class CMD_ShowSetQuality(ApiCall):
         # optional
         # this for whatever reason removes hdbluray not sdtv... which is just wrong. reverting to previous code.. plus we didnt use the new code everywhere.
         # self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", _getQualityMap().values()[1:])
-        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
-        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "hdwebdl", "hdbluray", "fullhdbluray", "unknown", "any"])
+        self.initial, args = self.check_params(args, kwargs, "initial", None, False, "list", ["sdtv", "sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray", "unknown"])
+        self.archive, args = self.check_params(args, kwargs, "archive", None, False, "list", ["sddvd", "hdtv", "rawhdtv", "fullhdtv", "hdwebdl", "fullhdwebdl", "hdbluray", "fullhdbluray"])
         # super, missing, help
         ApiCall.__init__(self, args, kwargs)
 
     def run(self):
-        """ set the quality for a show in sickbeard """
+        """ set the quality for a show in sickbeard by taking in a deliminated
+            string of qualities, map to their value and combine for new values
+        """
         showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(self.tvdbid))
         if not showObj:
             return _responds(RESULT_FAILURE, msg="Show not found")
 
-        """
-        take in a deliminated string of quality,
-        map that to the # it corresponds to,
-        then combine qualities to make a new quality
-
-        "self.initial": [
-          "sdtv",
-          "sddvd",
-          "hdtv",
-          "hdwebdl",
-          "hdbluray",
-          "unknown"
-        ],
-        "iqualityID": [
-            1,
-            2,
-            4,
-            8,
-            16,
-            32768
-        ],
-        "newQuality": 32799
-        """
-
         quality_map = {'sdtv': Quality.SDTV,
                        'sddvd': Quality.SDDVD,
                        'hdtv': Quality.HDTV,
+                       'rawhdtv': Quality.RAWHDTV,
+                       'fullhdtv': Quality.FULLHDTV,
                        'hdwebdl': Quality.HDWEBDL,
+                       'fullhdwebdl': Quality.FULLHDWEBDL,
                        'hdbluray': Quality.HDBLURAY,
                        'fullhdbluray': Quality.FULLHDBLURAY,
-                       'unknown': Quality.UNKNOWN,
-                       'any': ANY }
+                       'unknown': Quality.UNKNOWN}
 
         #use default quality as a failsafe
         newQuality = int(sickbeard.QUALITY_DEFAULT)
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index bf294978dfd556c9d00dfd7eb6eac887948d0b30..f9ff6310b6d6e4c8f166df13888c1499bae9e547 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -78,7 +78,7 @@ class PageTemplate (Template):
 
         if sickbeard.NZBS and sickbeard.NZBS_UID and sickbeard.NZBS_HASH:
             logger.log(u"NZBs.org has been replaced, please check the config to configure the new provider!", logger.ERROR)
-            ui.notifications.error("NZBs.org Config Update", "NZBs.org has a new site. Please <a href=\""+sickbeard.WEB_ROOT+"/config/providers\">update your config</a> with the api key from <a href=\"http://beta.nzbs.org/login\">http://beta.nzbs.org</a> and then disable the old NZBs.org provider.")
+            ui.notifications.error("NZBs.org Config Update", "NZBs.org has a new site. Please <a href=\""+sickbeard.WEB_ROOT+"/config/providers\">update your config</a> with the api key from <a href=\"http://nzbs.org/login\">http://nzbs.org</a> and then disable the old NZBs.org provider.")
 
         if "X-Forwarded-Host" in cherrypy.request.headers:
             self.sbHost = cherrypy.request.headers['X-Forwarded-Host']
@@ -215,19 +215,19 @@ class Manage:
         status_list = [int(whichStatus)]
         if status_list[0] == SNATCHED:
             status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
-        
+
         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(tvdb_id)] + status_list)
-        
+
         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] = {}
-            
+
             result[cur_season][cur_episode] = cur_result["name"]
-        
+
         return json.dumps(result)
 
     @cherrypy.expose
@@ -240,7 +240,7 @@ class Manage:
                 status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
         else:
             status_list = []
-        
+
         t = PageTemplate(file="manage_episodeStatuses.tmpl")
         t.submenu = ManageMenu
         t.whichStatus = whichStatus
@@ -248,7 +248,7 @@ class Manage:
         # if we have no status then this is as far as we need to go
         if not status_list:
             return _munge(t)
-        
+
         myDB = db.DBConnection()
         status_results = myDB.select("SELECT show_name, tv_shows.tvdb_id as tvdb_id FROM tv_episodes, tv_shows WHERE tv_episodes.status IN ("+','.join(['?']*len(status_list))+") AND season != 0 AND tv_episodes.showid = tv_shows.tvdb_id ORDER BY show_name", status_list)
 
@@ -261,11 +261,11 @@ class Manage:
                 ep_counts[cur_tvdb_id] = 1
             else:
                 ep_counts[cur_tvdb_id] += 1
-        
+
             show_names[cur_tvdb_id] = cur_status_result["show_name"]
             if cur_tvdb_id not in sorted_show_ids:
                 sorted_show_ids.append(cur_tvdb_id)
-        
+
         t.show_names = show_names
         t.ep_counts = ep_counts
         t.sorted_show_ids = sorted_show_ids
@@ -273,26 +273,26 @@ class Manage:
 
     @cherrypy.expose
     def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs):
-        
+
         status_list = [int(oldStatus)]
         if status_list[0] == SNATCHED:
             status_list = Quality.SNATCHED + Quality.SNATCHED_PROPER
 
         to_change = {}
-        
+
         # make a list of all shows and their associated args
         for arg in kwargs:
             tvdb_id, what = arg.split('-')
-            
+
             # we don't care about unchecked checkboxes
             if kwargs[arg] != 'on':
                 continue
-            
+
             if tvdb_id not in to_change:
                 to_change[tvdb_id] = []
-            
+
             to_change[tvdb_id].append(what)
-        
+
         myDB = db.DBConnection()
 
         for cur_tvdb_id in to_change:
@@ -304,19 +304,19 @@ class Manage:
                 to_change[cur_tvdb_id] = all_eps
 
             Home().setStatus(cur_tvdb_id, '|'.join(to_change[cur_tvdb_id]), newStatus, direct=True)
-            
+
         redirect('/manage/episodeStatuses')
 
     @cherrypy.expose
     def backlogShow(self, tvdb_id):
-        
+
         show_obj = helpers.findCertainShow(sickbeard.showList, int(tvdb_id))
-        
+
         if show_obj:
             sickbeard.backlogSearchScheduler.action.searchBacklog([show_obj]) #@UndefinedVariable
-        
+
         redirect("/manage/backlogOverview")
-        
+
     @cherrypy.expose
     def backlogOverview(self):
 
@@ -338,6 +338,7 @@ class Manage:
             epCounts[Overview.QUAL] = 0
             epCounts[Overview.GOOD] = 0
             epCounts[Overview.UNAIRED] = 0
+            epCounts[Overview.SNATCHED] = 0
 
             sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE showid = ? ORDER BY season DESC, episode DESC", [curShow.tvdbid])
 
@@ -386,11 +387,11 @@ class Manage:
         root_dir_list = []
 
         for curShow in showList:
-            
+
             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) 
-            
+                root_dir_list.append(cur_root_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
@@ -446,7 +447,7 @@ class Manage:
                 logger.log(u"For show "+showObj.name+" changing dir from "+showObj._location+" to "+new_show_dir)
             else:
                 new_show_dir = showObj._location
-            
+
             if paused == 'keep':
                 new_paused = showObj.paused
             else:
@@ -461,7 +462,7 @@ class Manage:
 
             if quality_preset == 'keep':
                 anyQualities, bestQualities = Quality.splitQuality(showObj.quality)
-            
+
             curErrors += Home().editShow(curShow, new_show_dir, anyQualities, bestQualities, new_flatten_folders, new_paused, directCall=True)
 
             if curErrors:
@@ -631,7 +632,7 @@ class ConfigGeneral:
     @cherrypy.expose
     def saveRootDirs(self, rootDirString=None):
         sickbeard.ROOT_DIRS = rootDirString
-    
+
     @cherrypy.expose
     def saveAddShowDefaults(self, defaultFlattenFolders, defaultStatus, anyQualities, bestQualities, audio_langs ):
 
@@ -646,7 +647,7 @@ class ConfigGeneral:
             bestQualities = []
 
         newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
-        
+
         sickbeard.STATUS_DEFAULT = int(defaultStatus)
         sickbeard.QUALITY_DEFAULT = int(newQuality)
         sickbeard.AUDIO_SHOW_DEFAULT = audio_langs
@@ -667,14 +668,14 @@ class ConfigGeneral:
             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)
 
@@ -727,14 +728,14 @@ class ConfigGeneral:
 
         sickbeard.USE_API = use_api
         sickbeard.API_KEY = api_key
-        
+
         if enable_https == "on":
             enable_https = 1
         else:
             enable_https = 0
-        
+
         sickbeard.ENABLE_HTTPS = enable_https
-        
+
         if not config.change_HTTPS_CERT(https_cert):
             results += ["Unable to create directory " + os.path.normpath(https_cert) + ", https cert dir not changed."]
 
@@ -898,7 +899,7 @@ class ConfigPostProcessing:
         sickbeard.metadata_provider_dict['Sony PS3'].set_config(sony_ps3_data)
         sickbeard.metadata_provider_dict['WDTV'].set_config(wdtv_data)
         sickbeard.metadata_provider_dict['TIVO'].set_config(tivo_data)
-        
+
         if self.isNamingValid(naming_pattern, naming_multi_ep) != "invalid":
             sickbeard.NAMING_PATTERN = naming_pattern
             sickbeard.NAMING_MULTI_EP = int(naming_multi_ep)
@@ -933,16 +934,16 @@ class ConfigPostProcessing:
 
         result = naming.test_name(pattern, multi, abd)
 
-        result = ek.ek(os.path.join, result['dir'], result['name']) 
+        result = ek.ek(os.path.join, result['dir'], result['name'])
 
         return result
-    
+
     @cherrypy.expose
     def isNamingValid(self, pattern=None, multi=None, abd=False):
         if pattern == None:
             return "invalid"
-        
-        # air by date shows just need one check, we don't need to worry about season folders 
+
+        # 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
@@ -950,7 +951,7 @@ class ConfigPostProcessing:
         else:
             # check validity of single and multi ep cases for the whole path
             is_valid = naming.check_valid_naming(pattern, multi)
-    
+
             # check validity of single and multi ep cases for only the file name
             require_season_folders = naming.check_force_season_folders(pattern, multi)
 
@@ -961,7 +962,7 @@ class ConfigPostProcessing:
         else:
             return "invalid"
 
-        
+
 class ConfigProviders:
 
     @cherrypy.expose
@@ -1031,9 +1032,11 @@ class ConfigProviders:
 
     @cherrypy.expose
     def saveProviders(self, nzbmatrix_username=None, nzbmatrix_apikey=None,
-                      nzbs_r_us_uid=None, nzbs_r_us_hash=None, newznab_string=None,
+                      nzbs_r_us_uid=None, nzbs_r_us_hash=None, newznab_string='',
+                      omgwtfnzbs_uid=None, omgwtfnzbs_key=None,
                       tvtorrents_digest=None, tvtorrents_hash=None,
- 					  btn_api_key=None, binnewz_language=None,
+                      torrentleech_key=None,
+                      btn_api_key=None, binnewz_language=None,
                       newzbin_username=None, newzbin_password=None,t411_language=None,t411_username=None,t411_password=None,
                       provider_order=None):
 
@@ -1092,10 +1095,16 @@ class ConfigProviders:
                 sickbeard.BINREQ = curEnabled
             elif curProvider == 'womble_s_index':
                 sickbeard.WOMBLE = curEnabled
+            elif curProvider == 'nzbx':
+                sickbeard.NZBX = curEnabled
+            elif curProvider == 'omgwtfnzbs':
+                sickbeard.OMGWTFNZBS = curEnabled
             elif curProvider == 'ezrss':
                 sickbeard.EZRSS = curEnabled
             elif curProvider == 'tvtorrents':
                 sickbeard.TVTORRENTS = curEnabled
+            elif curProvider == 'torrentleech':
+                sickbeard.TORRENTLEECH = curEnabled
             elif curProvider == 'btn':
                 sickbeard.BTN = curEnabled
             elif curProvider == 'binnewz':
@@ -1105,11 +1114,13 @@ class ConfigProviders:
             elif curProvider in newznabProviderDict:
                 newznabProviderDict[curProvider].enabled = bool(curEnabled)
             else:
-                logger.log(u"don't know what "+curProvider+" is, skipping")
+                logger.log(u"don't know what " + curProvider + " is, skipping")
 
         sickbeard.TVTORRENTS_DIGEST = tvtorrents_digest.strip()
         sickbeard.TVTORRENTS_HASH = tvtorrents_hash.strip()
 
+        sickbeard.TORRENTLEECH_KEY = torrentleech_key.strip()
+
         sickbeard.BTN_API_KEY = btn_api_key.strip()
         
         sickbeard.BINNEWZ_LANGUAGE = binnewz_language
@@ -1121,6 +1132,9 @@ class ConfigProviders:
         sickbeard.NZBSRUS_UID = nzbs_r_us_uid.strip()
         sickbeard.NZBSRUS_HASH = nzbs_r_us_hash.strip()
 
+        sickbeard.OMGWTFNZBS_UID = omgwtfnzbs_uid.strip()
+        sickbeard.OMGWTFNZBS_KEY = omgwtfnzbs_key.strip()
+
         sickbeard.PROVIDER_ORDER = provider_list
 
         sickbeard.save_config()
@@ -1135,6 +1149,7 @@ class ConfigProviders:
 
         redirect("/config/providers/")
 
+
 class ConfigNotifications:
 
     @cherrypy.expose
@@ -1144,20 +1159,21 @@ class ConfigNotifications:
         return _munge(t)
 
     @cherrypy.expose
-    def saveNotifications(self, use_xbmc=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None,
+    def saveNotifications(self, use_xbmc=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=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_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_host=None, growl_password=None, 
-                          use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_api=None, prowl_priority=0, 
-                          use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None, 
+                          use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None, growl_host=None, growl_password=None,
+                          use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_api=None, prowl_priority=0,
+                          use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None,
                           use_notifo=None, notifo_notify_onsnatch=None, notifo_notify_ondownload=None, notifo_username=None, notifo_apisecret=None,
                           use_boxcar=None, boxcar_notify_onsnatch=None, boxcar_notify_ondownload=None, boxcar_username=None,
                           use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None, pushover_userkey=None,
                           use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=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,
-                          use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None, pytivo_update_library=None, 
+                          use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=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_api=None, nma_priority=0 ):
 
@@ -1183,6 +1199,11 @@ class ConfigNotifications:
         else:
             xbmc_update_full = 0
 
+        if xbmc_update_onlyfirst == "on":
+            xbmc_update_onlyfirst = 1
+        else:
+            xbmc_update_onlyfirst = 0
+
         if use_xbmc == "on":
             use_xbmc = 1
         else:
@@ -1222,7 +1243,7 @@ class ConfigNotifications:
             use_growl = 1
         else:
             use_growl = 0
-            
+
         if prowl_notify_onsnatch == "on":
             prowl_notify_onsnatch = 1
         else:
@@ -1303,6 +1324,11 @@ class ConfigNotifications:
         else:
             use_synoindex = 0
 
+        if use_nmjv2 == "on":
+            use_nmjv2 = 1
+        else:
+            use_nmjv2 = 0
+
         if use_trakt == "on":
             use_trakt = 1
         else:
@@ -1312,7 +1338,7 @@ class ConfigNotifications:
             use_pytivo = 1
         else:
             use_pytivo = 0
-            
+
         if pytivo_notify_onsnatch == "on":
             pytivo_notify_onsnatch = 1
         else:
@@ -1348,6 +1374,7 @@ class ConfigNotifications:
         sickbeard.XBMC_NOTIFY_ONDOWNLOAD = xbmc_notify_ondownload
         sickbeard.XBMC_UPDATE_LIBRARY = xbmc_update_library
         sickbeard.XBMC_UPDATE_FULL = xbmc_update_full
+        sickbeard.XBMC_UPDATE_ONLYFIRST = xbmc_update_onlyfirst
         sickbeard.XBMC_HOST = xbmc_host
         sickbeard.XBMC_USERNAME = xbmc_username
         sickbeard.XBMC_PASSWORD = xbmc_password
@@ -1404,6 +1431,11 @@ class ConfigNotifications:
 
         sickbeard.USE_SYNOINDEX = use_synoindex
 
+        sickbeard.USE_NMJv2 = use_nmjv2
+        sickbeard.NMJv2_HOST = nmjv2_host
+        sickbeard.NMJv2_DATABASE = nmjv2_database
+        sickbeard.NMJv2_DBLOC = nmjv2_dbloc
+
         sickbeard.USE_TRAKT = use_trakt
         sickbeard.TRAKT_USERNAME = trakt_username
         sickbeard.TRAKT_PASSWORD = trakt_password
@@ -1411,7 +1443,7 @@ class ConfigNotifications:
 
         sickbeard.USE_PYTIVO = use_pytivo
         sickbeard.PYTIVO_NOTIFY_ONSNATCH = pytivo_notify_onsnatch == "off"
-        sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = pytivo_notify_ondownload ==  "off"
+        sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = pytivo_notify_ondownload == "off"
         sickbeard.PYTIVO_UPDATE_LIBRARY = pytivo_update_library
         sickbeard.PYTIVO_HOST = pytivo_host
         sickbeard.PYTIVO_SHARE_NAME = pytivo_share_name
@@ -1422,7 +1454,7 @@ class ConfigNotifications:
         sickbeard.NMA_NOTIFY_ONDOWNLOAD = nma_notify_ondownload
         sickbeard.NMA_API = nma_api
         sickbeard.NMA_PRIORITY = nma_priority
-        
+
         sickbeard.save_config()
 
         if len(results) > 0:
@@ -1448,7 +1480,7 @@ class Config:
     general = ConfigGeneral()
 
     search = ConfigSearch()
-    
+
     postProcessing = ConfigPostProcessing()
 
     providers = ConfigProviders()
@@ -1581,16 +1613,16 @@ class NewHomeAddShows:
     def massAddTable(self, rootDir=None):
         t = PageTemplate(file="home_massAddTable.tmpl")
         t.submenu = HomeMenu()
-        
+
         myDB = db.DBConnection()
 
         if not rootDir:
-            return "No folders selected." 
+            return "No folders selected."
         elif type(rootDir) != list:
             root_dirs = [rootDir]
         else:
             root_dirs = rootDir
-        
+
         root_dirs = [urllib.unquote_plus(x) for x in root_dirs]
 
         default_index = int(sickbeard.ROOT_DIRS.split('|')[0])
@@ -1599,9 +1631,9 @@ class NewHomeAddShows:
             if tmp in root_dirs:
                 root_dirs.remove(tmp)
                 root_dirs = [tmp]+root_dirs
-        
+
         dir_list = []
-        
+
         for root_dir in root_dirs:
             try:
                 file_list = ek.ek(os.listdir, root_dir)
@@ -1613,36 +1645,36 @@ class NewHomeAddShows:
                 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
-                
+
                 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),
                            }
-                
+
                 # see if the folder is in XBMC already
                 dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path])
-                
+
                 if dirResults:
                     cur_dir['added_already'] = True
                 else:
                     cur_dir['added_already'] = False
-                
+
                 dir_list.append(cur_dir)
-                
+
                 tvdb_id = ''
                 show_name = ''
                 for cur_provider in sickbeard.metadata_provider_dict.values():
                     (tvdb_id, show_name) = cur_provider.retrieveShowMetadata(cur_path)
                     if tvdb_id and show_name:
                         break
-                
+
                 cur_dir['existing_info'] = (tvdb_id, show_name)
-                
+
                 if tvdb_id and helpers.findCertainShow(sickbeard.showList, tvdb_id):
-                    cur_dir['added_already'] = True 
+                    cur_dir['added_already'] = True
 
         t.dirList = dir_list
-        
+
         return _munge(t)
 
     @cherrypy.expose
@@ -1653,38 +1685,38 @@ class NewHomeAddShows:
         """
         t = PageTemplate(file="home_newShow.tmpl")
         t.submenu = HomeMenu()
-        
+
         show_dir, tvdb_id, show_name = self.split_extra_show(show_to_add)
-        
+
         if tvdb_id and show_name:
             use_provided_info = True
         else:
             use_provided_info = False
-        
+
         # tell the template whether we're giving it show name & TVDB ID
         t.use_provided_info = use_provided_info
-        
-        # use the given show_dir for the tvdb search if available 
+
+        # use the given show_dir for the tvdb search if available
         if not show_dir:
             t.default_show_name = ''
         elif not show_name:
             t.default_show_name = ek.ek(os.path.basename, ek.ek(os.path.normpath, show_dir)).replace('.',' ')
         else:
             t.default_show_name = show_name
-        
+
         # carry a list of other dirs if given
         if not other_shows:
             other_shows = []
         elif type(other_shows) != list:
             other_shows = [other_shows]
-        
+
         if use_provided_info:
             t.provided_tvdb_id = tvdb_id
             t.provided_tvdb_name = show_name
-            
+
         t.provided_show_dir = show_dir
         t.other_shows = other_shows
-        
+
         return _munge(t)
 
     @cherrypy.expose
@@ -1695,52 +1727,52 @@ class NewHomeAddShows:
         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.
         """
-        
+
         # grab our list of other dirs if given
         if not other_shows:
             other_shows = []
         elif type(other_shows) != list:
             other_shows = [other_shows]
-            
-        def finishAddShow(): 
+
+        def finishAddShow():
             # if there are no extra shows then go home
             if not other_shows:
                 redirect('/home')
-            
+
             # peel off the next one
             next_show_dir = other_shows[0]
             rest_of_show_dirs = other_shows[1:]
-            
+
             # go to add the next show
             return self.newShow(next_show_dir, rest_of_show_dirs)
-        
+
         # if we're skipping then behave accordingly
         if skipShow:
             return finishAddShow()
-        
+
         # sanity check on our inputs
         if (not rootDir and not fullShowPath) or not whichSeries:
             return "Missing params, no tvdb id or folder:"+repr(whichSeries)+" and "+repr(rootDir)+"/"+repr(fullShowPath)
-        
+
         # figure out what show we're adding and where
         series_pieces = whichSeries.partition('|')
         if len(series_pieces) < 3:
             return "Error with show selection."
-        
+
         tvdb_id = int(series_pieces[0])
         show_name = series_pieces[2]
-        
+
         # 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))
-        
+
         # 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')
-        
+
         # 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")
@@ -1758,7 +1790,7 @@ class NewHomeAddShows:
             flatten_folders = 1
         else:
             flatten_folders = 0
-        
+
         if not anyQualities:
             anyQualities = []
         if not bestQualities:
@@ -1768,22 +1800,22 @@ class NewHomeAddShows:
         if type(bestQualities) != list:
             bestQualities = [bestQualities]
         newQuality = Quality.combineQualities(map(int, anyQualities), map(int, bestQualities))
-        
+
         # add the show
         sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, int(defaultStatus), newQuality, flatten_folders, tvdbLang, audio_lang) #@UndefinedVariable
         ui.notifications.message('Show added', 'Adding the specified show into '+show_dir)
 
         return finishAddShow()
-        
+
 
     @cherrypy.expose
     def existingShows(self):
         """
-        Prints out the page to add existing shows from a root dir 
+        Prints out the page to add existing shows from a root dir
         """
         t = PageTemplate(file="home_addExistingShow.tmpl")
         t.submenu = HomeMenu()
-        
+
         return _munge(t)
 
     def split_extra_show(self, extra_show):
@@ -1795,7 +1827,7 @@ class NewHomeAddShows:
         show_dir = split_vals[0]
         tvdb_id = split_vals[1]
         show_name = '|'.join(split_vals[2:])
-        
+
         return (show_dir, tvdb_id, show_name)
 
     @cherrypy.expose
@@ -1810,14 +1842,14 @@ class NewHomeAddShows:
             shows_to_add = []
         elif type(shows_to_add) != list:
             shows_to_add = [shows_to_add]
-        
+
         shows_to_add = [urllib.unquote_plus(x) for x in shows_to_add]
-        
+
         if promptForSettings == "on":
             promptForSettings = 1
         else:
             promptForSettings = 0
-        
+
         tvdb_id_given = []
         dirs_only = []
         # separate all the ones with TVDB IDs
@@ -1834,7 +1866,7 @@ class NewHomeAddShows:
         # 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:])
-        
+
         # 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 tvdb_id_given:
@@ -1843,7 +1875,7 @@ class NewHomeAddShows:
             # add the show
             sickbeard.showQueueScheduler.action.addShow(tvdb_id, show_dir, SKIPPED, sickbeard.QUALITY_DEFAULT, sickbeard.FLATTEN_FOLDERS_DEFAULT) #@UndefinedVariable
             num_added += 1
-         
+
         if num_added:
             ui.notifications.message("Shows Added", "Automatically added "+str(num_added)+" from their existing metadata files")
 
@@ -2119,6 +2151,25 @@ class Home:
         else:
             return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}'
 
+    @cherrypy.expose
+    def testNMJv2(self, host=None):
+        cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
+
+        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)
+
+    @cherrypy.expose
+    def settingsNMJv2(self, host=None, dbloc=None, instance=None):
+        cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
+        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}
+
     @cherrypy.expose
     def testTrakt(self, api=None, username=None, password=None):
         cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
@@ -2132,7 +2183,7 @@ class Home:
     @cherrypy.expose
     def testNMA(self, nma_api=None, nma_priority=0):
         cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
-        
+
         result = notifiers.nma_notifier.test_notify(nma_api, nma_priority)
         if result:
             return "Test NMA notice sent successfully"
@@ -2250,6 +2301,7 @@ class Home:
         epCounts[Overview.QUAL] = 0
         epCounts[Overview.GOOD] = 0
         epCounts[Overview.UNAIRED] = 0
+        epCounts[Overview.SNATCHED] = 0
 
         for curResult in sqlResults:
 
@@ -2461,17 +2513,20 @@ class Home:
         # just give it some time
         time.sleep(3)
 
-        redirect("/home/displayShow?show="+str(showObj.tvdbid))
+        redirect("/home/displayShow?show=" + str(showObj.tvdbid))
 
     @cherrypy.expose
     def updateXBMC(self, showName=None):
-        # TODO: configure that each host can have different options / username / pw
-        # only send update to first host in the list -- workaround for xbmc sql backend users
-        firstHost = sickbeard.XBMC_HOST.split(",")[0].strip()
+        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 notifiers.xbmc_notifier.update_library(showName=showName):
-            ui.notifications.message("Library update command sent to XBMC host: " + firstHost)
+            ui.notifications.message("Library update command sent to XBMC host(s): " + host)
         else:
-            ui.notifications.error("Unable to contact XBMC host: " + firstHost)
+            ui.notifications.error("Unable to contact one or more XBMC host(s): " + host)
         redirect('/home')
 
     @cherrypy.expose
@@ -2529,7 +2584,7 @@ class Home:
                         ep_segment = str(epObj.airdate)[:7]
                     else:
                         ep_segment = epObj.season
-    
+
                     if ep_segment not in segment_list:
                         segment_list.append(ep_segment)
 
@@ -2673,18 +2728,18 @@ class Home:
 
         if eps == None:
             redirect("/home/displayShow?show=" + show)
-            
+
         for curEp in eps.split('|'):
 
             epInfo = curEp.split('x')
-            
+
             # 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]])
-            
+
             root_ep_obj = show_obj.getEpisode(int(epInfo[0]), int(epInfo[1]))
             for cur_related_ep in related_eps_result:
                 related_ep_obj = show_obj.getEpisode(int(cur_related_ep["season"]), int(cur_related_ep["episode"]))
@@ -2698,7 +2753,7 @@ class Home:
     @cherrypy.expose
     def searchEpisode(self, show=None, season=None, episode=None):
 
-        # retrieve the episode object and fail if we can't get one 
+        # 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'})
@@ -2718,13 +2773,13 @@ class Home:
         return json.dumps({'result': 'failure'})
 
 class UI:
-    
+
     @cherrypy.expose
     def add_message(self):
-        
+
         ui.notifications.message('Test 1', 'This is test number 1')
         ui.notifications.error('Test 2', 'This is test number 2')
-        
+
         return "ok"
 
     @cherrypy.expose
@@ -2803,32 +2858,32 @@ class WebInterface:
     def setComingEpsLayout(self, layout):
         if layout not in ('poster', 'banner', 'list'):
             layout = 'banner'
-        
+
         sickbeard.COMING_EPS_LAYOUT = layout
-        
+
         redirect("/comingEpisodes")
 
     @cherrypy.expose
     def toggleComingEpsDisplayPaused(self):
-        
+
         sickbeard.COMING_EPS_DISPLAY_PAUSED = not sickbeard.COMING_EPS_DISPLAY_PAUSED
-        
+
         redirect("/comingEpisodes")
 
     @cherrypy.expose
     def setComingEpsSort(self, sort):
         if sort not in ('date', 'network', 'show'):
             sort = 'date'
-        
+
         sickbeard.COMING_EPS_SORT = sort
-        
+
         redirect("/comingEpisodes")
 
     @cherrypy.expose
     def comingEpisodes(self, layout="None"):
 
         myDB = db.DBConnection()
-        
+
         today = datetime.date.today().toordinal()
         next_week = (datetime.date.today() + datetime.timedelta(days=7)).toordinal()
         recently = (datetime.date.today() - datetime.timedelta(days=3)).toordinal()
@@ -2882,7 +2937,7 @@ class WebInterface:
             t.layout = layout
         else:
             t.layout = sickbeard.COMING_EPS_LAYOUT
-                
+
 
         return _munge(t)
 
@@ -2893,11 +2948,11 @@ class WebInterface:
     config = Config()
 
     home = Home()
-    
+
     api = Api()
 
     browser = browser.WebFileBrowser()
 
     errorlogs = ErrorLogs()
-    
+
     ui = UI()