diff --git a/.travis.yml b/.travis.yml
index 613c5001fa70c6b931f39492bd816d763a01c587..6492a5f6dceec7105385ad7c3687c75e7c5b8420 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,7 @@ before_script:
  - chmod +x ./tests/all_tests.py
 
 script:
-  - ./tests/all_tests.py
+  - ./tests/all_tests.py || cat ./Logs/sickrage.log
 
 notifications:
-  irc: "irc.freenode.net#sickrage-updates"
\ No newline at end of file
+  irc: "irc.freenode.net#sickrage-updates"
diff --git a/CHANGES.md b/CHANGES.md
index 00e17cb6c1ef661861aa477843d67e6a15778c33..08717d2e6cc6e435417546424a2c417cd972508c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,39 @@
+### 4.0.18 (2015-04-26)
+
+[full changelog](https://github.com/SiCKRAGETV/SickRage/compare/v4.0.17...v4.0.18)
+
+* Fixed build tests
+* Fixed T411 wrong api data received
+* Fixed import error in subtitles
+* Fixed Plex auth token
+* Fixed log message error
+* Fixed SeasonPack search error
+* Fixed morethantv tracker
+* Fixed Kodi error
+* Fixed Scenetime tracker
+* Fixed issue submitter and don't allow dupe submissions
+* Fixed masking NZB API Keys in logs not working sometimes
+* Fixed upstart script
+* Added check to issue submitter to wait issue is fully submitted
+* Added SxEE to IPT
+* Added new network logos
+* Added SD bluray quality to regex
+* Added  Persistent Collapse Expand Buttons on Episode management
+* Enable trending shows only if Trakt is enabled
+* Changed KAT url to new domain page
+* Changed to 'WARNING' logger for 'No NZB/Torrent providers found or enabled' error
+* Changed BTN api limit error to warning
+* Changed plex user errors to warning instead of error
+* Removed "indexer" tag from metadata files
+* Feature: update only specific plex section
+* Feature: add home page filtering (persisted)
+* Feature: add option to collapse previous seasons in displayshow
+* Feature: "Recommended shows" use the same template as "Trending shows"
+* Feature: Ask user before delete show in mass edit
+* Feature: Seperate Plex Media Server and Plex Media Client in UI
+* Feature: filter shows with >, >=, <=, < ,  xx to yy , xx - yy, =
+* Note: signs should be first, followed by a space, then the value.
+
 ### 4.0.17 (2015-04-19)
 
 [full changelog](https://github.com/SiCKRAGETV/SickRage/compare/v4.0.16...v4.0.17)
diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css
index 6a320fef293220ca1dda1ca6b9e6114a7366c03b..6834c26662ba87810cb28f78491542eb02df69bf 100644
--- a/gui/slick/css/dark.css
+++ b/gui/slick/css/dark.css
@@ -1935,32 +1935,19 @@ div.blackwhitelist{
 	float:left;
 	text-align: center; 
 }
-
 div.blackwhitelist input {
-    margin: 5px 5px;
+    margin: 5px 0px;
 }
-
 div.blackwhitelist.pool select{
-    width: 300px;
+    min-width: 230px;
 }
-
-div.blackwhitelist.pool {
-    margin:5px;
-}
-
 div.blackwhitelist.white select, div.blackwhitelist.black select {
-	width: 180px;
+	min-width: 150px;
 }
-
-div.blackwhitelist.white, div.blackwhitelist.black {
-	margin:5px;
-}
-
 div.blackwhitelist span {
 	display: block;
 	text-align: center;
 }
-
 div.blackwhitelist.anidb, div.blackwhitelist.manual {
 	margin: 7px 0px;
 }
diff --git a/gui/slick/css/light.css b/gui/slick/css/light.css
index 1c49d9714af641a0a414e4b1eb8d2fc6a20c2197..34cd29d919f3e3a0179af6ca8c3f5ef422a4784e 100644
--- a/gui/slick/css/light.css
+++ b/gui/slick/css/light.css
@@ -1902,32 +1902,19 @@ div.blackwhitelist{
 	float:left;
 	text-align: center; 
 }
-
 div.blackwhitelist input {
-    margin: 5px 5px;
+    margin: 5px 0px;
 }
-
 div.blackwhitelist.pool select{
-    width: 300px;
+    width: 230px;
 }
-
-div.blackwhitelist.pool {
-    margin:5px;
-}
-
 div.blackwhitelist.white select, div.blackwhitelist.black select {
-	width: 180px;
-}
-
-div.blackwhitelist.white, div.blackwhitelist.black {
-	margin:5px;
+	width: 150px;
 }
-
 div.blackwhitelist span {
 	display: block;
 	text-align: center;
 }
-
 div.blackwhitelist.anidb, div.blackwhitelist.manual {
 	margin: 7px 0px;
 }
diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css
index 4216d0c4ac1585481bdbde9cac540c47e4bf9113..6c7b6a2d2a4d6ed6602b32ff1a16c88f837c0569 100644
--- a/gui/slick/css/style.css
+++ b/gui/slick/css/style.css
@@ -1983,32 +1983,19 @@ div.blackwhitelist{
 	float:left;
 	text-align: center; 
 }
-
 div.blackwhitelist input {
-	margin: 5px 5px;
+	margin: 5px 0px;
 }
-
 div.blackwhitelist.pool select{
-	width: 300px;
+	width: 230px;
 }
-
-div.blackwhitelist.pool {
-	margin:5px;
-}
-
 div.blackwhitelist.white select, div.blackwhitelist.black select {
-	width: 180px;
-}
-
-div.blackwhitelist.white, div.blackwhitelist.black {
-	margin:5px;
+	width: 150px;
 }
-
 div.blackwhitelist span {
 	display: block;
 	text-align: center;
 }
-
 div.blackwhitelist.anidb, div.blackwhitelist.manual {
 	margin: 7px 0px;
 }
diff --git a/gui/slick/images/network/abc (us).png b/gui/slick/images/network/abc (us).png
new file mode 100644
index 0000000000000000000000000000000000000000..ca30b23bb325dcf1faa5549e6490e0912faad7cf
Binary files /dev/null and b/gui/slick/images/network/abc (us).png differ
diff --git a/gui/slick/images/network/fox (us).png b/gui/slick/images/network/fox (us).png
new file mode 100644
index 0000000000000000000000000000000000000000..2626578519b6969b2eb3a4da2766044e98d06248
Binary files /dev/null and b/gui/slick/images/network/fox (us).png differ
diff --git a/gui/slick/images/network/kids station.png b/gui/slick/images/network/kids station.png
new file mode 100644
index 0000000000000000000000000000000000000000..187a58a8e06745c98d0476dabd7dab7df22b84d7
Binary files /dev/null and b/gui/slick/images/network/kids station.png differ
diff --git a/gui/slick/images/network/kyoto broadcasting system.png b/gui/slick/images/network/kyoto broadcasting system.png
new file mode 100644
index 0000000000000000000000000000000000000000..fc6dc2eb37d9a42a843d868738a85da2eced6d2b
Binary files /dev/null and b/gui/slick/images/network/kyoto broadcasting system.png differ
diff --git a/gui/slick/images/network/mtv (us).png b/gui/slick/images/network/mtv (us).png
new file mode 100644
index 0000000000000000000000000000000000000000..0c36f2e1ca96d36b49218a319f46b9c0e782d95d
Binary files /dev/null and b/gui/slick/images/network/mtv (us).png differ
diff --git a/gui/slick/images/network/showcase (ca).png b/gui/slick/images/network/showcase (ca).png
new file mode 100644
index 0000000000000000000000000000000000000000..5ae62e0c4b6e00bf8f1e82d73b000aa44e83eff7
Binary files /dev/null and b/gui/slick/images/network/showcase (ca).png differ
diff --git a/gui/slick/images/network/toei channel.png b/gui/slick/images/network/toei channel.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb3eaf98f2a175e6c282a0716c9942e18f3950b6
Binary files /dev/null and b/gui/slick/images/network/toei channel.png differ
diff --git a/gui/slick/images/network/tv kanagawa.png b/gui/slick/images/network/tv kanagawa.png
new file mode 100644
index 0000000000000000000000000000000000000000..27bb05da53f7223f8dbe5c59a9d02db3d341d45b
Binary files /dev/null and b/gui/slick/images/network/tv kanagawa.png differ
diff --git a/gui/slick/interfaces/default/config_general.tmpl b/gui/slick/interfaces/default/config_general.tmpl
index 3948f456e21749e879ee65fb39d716018da2ba6f..d7c04f944fbe44df8c83df0ef25662d75b1b986a 100644
--- a/gui/slick/interfaces/default/config_general.tmpl
+++ b/gui/slick/interfaces/default/config_general.tmpl
@@ -285,7 +285,15 @@
 								</span>
 							</label>
 						</div>
-
+						<div class="field-pair">
+							<label for="display_all_seasons">
+								<span class="component-title">Show all seasons</span>
+								<span class="component-desc">
+									<input type="checkbox" name="display_all_seasons" id="display_all_seasons" #if $sickbeard.DISPLAY_ALL_SEASONS then 'checked="checked"' else ''#>
+									<p>on the show summary page</p>
+								</span>
+							</label>
+						</div>
 						<div class="field-pair">
 							<label for="sort_article">
 								<span class="component-title">Sort with "The", "A", "An"</span>
diff --git a/gui/slick/interfaces/default/config_notifications.tmpl b/gui/slick/interfaces/default/config_notifications.tmpl
index 5b82eb6f80418f4d54a018a137acb373086d87f2..9c989c1b6ece2238f097d5a3f81df789cf0c78e7 100644
--- a/gui/slick/interfaces/default/config_notifications.tmpl
+++ b/gui/slick/interfaces/default/config_notifications.tmpl
@@ -189,59 +189,79 @@
                                     <span class="component-desc">(<a href="<%= anon_url('https://support.plex.tv/hc/en-us/articles/204059436-Finding-your-account-token-X-Plex-Token') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"><u>Finding your account token</u></a>)</span>
                                 </label>
                             </div>                        
-							<div class="component-group" style="padding: 0; min-height: 130px">
-								<div class="field-pair">
-									<label for="plex_username">
-										<span class="component-title">Server/client username</span>
-										<span class="component-desc">
-											<input type="text" name="plex_username" id="plex_username" value="$sickbeard.PLEX_USERNAME" class="form-control input-sm input250" />
-											<p>blank = no authentication</p>
-										</span>
-									</label>
-								</div>
-								<div class="field-pair">
-									<label for="plex_password">
-										<span class="component-title">Server/client password</span>
-										<span class="component-desc">
-											<input type="password" name="plex_password" id="plex_password" value="#echo '*' * len($sickbeard.PLEX_PASSWORD)#" class="form-control input-sm input250" />
-											<p>blank = no authentication</p>
-										</span>
-									</label>
-								</div>
-							</div>
+                            <div class="component-group" style="padding: 0; min-height: 130px">
+                                <div class="field-pair">
+                                    <label for="plex_username">
+                                        <span class="component-title">Server Username</span>
+                                        <span class="component-desc">
+                                            <input type="text" name="plex_username" id="plex_username" value="$sickbeard.PLEX_USERNAME" class="form-control input-sm input250" />
+                                            <p>blank = no authentication</p>
+                                        </span>
+                                    </label>
+                                </div>
+                                <div class="field-pair">
+                                    <label for="plex_password">
+                                        <span class="component-title">Server/client password</span>
+                                        <span class="component-desc">
+                                            <input type="password" name="plex_password" id="plex_password" value="#echo '*' * len($sickbeard.PLEX_PASSWORD)#" class="form-control input-sm input250" />
+                                            <p>blank = no authentication</p>
+                                        </span>
+                                    </label>
+                                </div>
+                            </div>
                         
-							<div class="component-group" style="padding: 0; min-height: 50px">
-								<div class="field-pair">
-									<label for="plex_update_library">
-										<span class="component-title">Update server library</span>
-										<span class="component-desc">
-											<input type="checkbox" class="enabler" name="plex_update_library" id="plex_update_library" #if $sickbeard.PLEX_UPDATE_LIBRARY then 'checked="checked" ' else ''#/>
-											<p>update Plex Media Server library when a download finishes</p>
-										</span>
-									</label>
-								</div>
-								<div id="content_plex_update_library">
-									<div class="field-pair">
-										<label for="plex_server_host">
-											<span class="component-title">Plex Media Server IP:Port</span>
-											<span class="component-desc">
-												<input type="text" name="plex_server_host" id="plex_server_host" value="<%= re.sub(r'\b,\b', ', ', sickbeard.PLEX_SERVER_HOST) %>" class="form-control input-sm input350" />
-												<div class="clear-left">
-													<p>one or more hosts running Plex Media Server<br />(eg. 192.168.1.1:32400, 192.168.1.2:32400)</p>
-												</div>
-											</span>
-										</label>
-									</div>
-
-									<div class="field-pair">
-										<div class="testNotification" id="testPMS-result">Click below to test Plex server(s)</div>
-										<input class="btn" type="button" value="Test Plex Server" id="testPMS" />
-										<input type="submit" class="config_submitter btn" value="Save Changes" />
-										<div class="clear-left">&nbsp;</div>
-									</div>
-								</div>
-							</div>
-                                    
+                            <div class="component-group" style="padding: 0; min-height: 50px">
+                                <div class="field-pair">
+                                    <label for="plex_update_library">
+                                        <span class="component-title">Update server library</span>
+                                        <span class="component-desc">
+                                            <input type="checkbox" class="enabler" name="plex_update_library" id="plex_update_library" #if $sickbeard.PLEX_UPDATE_LIBRARY then 'checked="checked" ' else ''#/>
+                                            <p>update Plex Media Server library when a download finishes</p>
+                                        </span>
+                                    </label>
+                                </div>
+                                <div id="content_plex_update_library">
+                                    <div class="field-pair">
+                                        <label for="plex_server_host">
+                                            <span class="component-title">Plex Media Server IP:Port</span>
+                                            <span class="component-desc">
+                                                <input type="text" name="plex_server_host" id="plex_server_host" value="<%= re.sub(r'\b,\b', ', ', sickbeard.PLEX_SERVER_HOST) %>" class="form-control input-sm input350" />
+                                                <div class="clear-left">
+                                                    <p>one or more hosts running Plex Media Server<br />(eg. 192.168.1.1:32400, 192.168.1.2:32400)</p>
+                                                </div>
+                                            </span>
+                                        </label>
+                                    </div>
+
+                                    <div class="field-pair">
+                                        <div class="testNotification" id="testPMS-result">Click below to test Plex server(s)</div>
+                                        <input class="btn" type="button" value="Test Plex Server" id="testPMS" />
+                                        <input type="submit" class="config_submitter btn" value="Save Changes" />
+                                        <div class="clear-left">&nbsp;</div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div><!-- /content_use_plex -->
+                    </fieldset>
+                </div><!-- /plex media server component-group -->
+                
+                <div class="component-group">
+                    <div class="component-group-desc">
+                        <img class="notifier-icon" src="$sbRoot/images/notifiers/plex.png" alt="" title="Plex Media Client" />
+                        <h3><a href="<%= anon_url('http://www.plexapp.com/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">Plex Media Client</a></h3>
+                    </div>
+                    <fieldset class="component-group-list">
+                        <div class="field-pair">
+                            <label for="use_plex_client">
+                                <span class="component-title">Enable</span>
+                                <span class="component-desc">
+                                    <input type="checkbox" class="enabler" name="use_plex" id="use_plex_client" #if $sickbeard.USE_PLEX_CLIENT then "checked=\"checked\"" else ""# />
+                                    <p>should SickRage send Plex commands ?</p>
+                                </span>
+                            </label>
+                        </div>
+                        
+                        <div id="content_use_plex_client">
                             <div class="field-pair">
                                 <label for="plex_notify_onsnatch">
                                     <span class="component-title">Notify on snatch</span>
@@ -280,16 +300,36 @@
                                     </span>
                                 </label>
                             </div>
-
+                            <div class="component-group" style="padding: 0; min-height: 130px">
+                                <div class="field-pair">
+                                    <label for="plex_username">
+                                        <span class="component-title">Server Username</span>
+                                        <span class="component-desc">
+                                            <input type="text" name="plex_client_username" id="plex_client_username" value="$sickbeard.PLEX_CLIENT_USERNAME" class="form-control input-sm input250" />
+                                            <p>blank = no authentication</p>
+                                        </span>
+                                    </label>
+                                </div>
+                                <div class="field-pair">
+                                    <label for="plex_client_password">
+                                        <span class="component-title">Client Password</span>
+                                        <span class="component-desc">
+                                            <input type="password" name="plex_client_password" id="plex_client_password" value="#echo '*' * len($sickbeard.PLEX_CLIENT_PASSWORD)#" class="form-control input-sm input250" />
+                                            <p>blank = no authentication</p>
+                                        </span>
+                                    </label>
+                                </div>
+                            </div>
+                            
                             <div class="field-pair">
                                 <div class="testNotification" id="testPMC-result">Click below to test Plex client(s)</div>
                                 <input class="btn" type="button" value="Test Plex Client" id="testPMC" />
                                 <input type="submit" class="config_submitter btn" value="Save Changes" />
                                 <div class=clear-left><p>Note: some Plex clients <b class="boldest">do not</b> support notifications e.g. Plexapp for Samsung TVs</p></div>
                             </div>
-                        </div><!-- /content_use_plex -->
+                        </div><!-- /content_use_plex_client -->
                     </fieldset>
-                </div><!-- /plex component-group -->
+                </div><!-- /plex client component-group -->
 
 
                 <div class="component-group">
@@ -1542,11 +1582,11 @@
                                 </label>
                             </div>
                             <div id="content_trakt_use_rolling_download">
-              	                <div class="field-pair">
+                                  <div class="field-pair">
                                     <label for="trakt_rolling_num_ep">
                                         <span class="component-title">Number of Episode:</span>
                                         <span class="component-desc">
-                                	    <input type="number" name="trakt_rolling_num_ep" id="trakt_rolling_num_ep" value="$sickbeard.TRAKT_ROLLING_NUM_EP" class="form-control input-sm input75"/>
+                                        <input type="number" name="trakt_rolling_num_ep" id="trakt_rolling_num_ep" value="$sickbeard.TRAKT_ROLLING_NUM_EP" class="form-control input-sm input75"/>
                                     </label>
                                     <label>
                                         <span class="component-title">&nbsp;</span>
@@ -1562,11 +1602,11 @@
                                         <span class="component-desc">Hours between check. (Cannot be lower than 4 hours)</span>
                                     </p>
                                 </div>
-              	                <div class="field-pair">
+                                  <div class="field-pair">
                                     <label for="trakt_rolling_add_paused">
                                         <span class="component-title">Should new show to be added paused?:</span>
                                         <span class="component-desc">
-                                	    <input type="checkbox" name="trakt_rolling_add_paused" id="trakt_rolling_add_paused" #if $sickbeard.TRAKT_ROLLING_ADD_PAUSED then "checked=\"checked\"" else ""# />
+                                        <input type="checkbox" name="trakt_rolling_add_paused" id="trakt_rolling_add_paused" #if $sickbeard.TRAKT_ROLLING_ADD_PAUSED then "checked=\"checked\"" else ""# />
                                     </label>
                                     <label>
                                         <span class="component-title">&nbsp;</span>
diff --git a/gui/slick/interfaces/default/displayShow.tmpl b/gui/slick/interfaces/default/displayShow.tmpl
index 358389403416a46f0658d96a6e7e579b9c9443f9..2111f4253f1991bbd7c204c80089dee5bd2c393c 100644
--- a/gui/slick/interfaces/default/displayShow.tmpl
+++ b/gui/slick/interfaces/default/displayShow.tmpl
@@ -17,6 +17,7 @@
 
 <script type="text/javascript" src="$sbRoot/js/lib/jquery.bookmarkscroll.js?$sbPID"></script>
 
+
 <input type="hidden" id="sbRoot" value="$sbRoot" />
 
 <script type="text/javascript" src="$sbRoot/js/displayShow.js?$sbPID"></script>
@@ -25,6 +26,7 @@
 <script type="text/javascript" src="$sbRoot/js/ratingTooltip.js?$sbPID"></script>
 <script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?$sbPID"></script>
 <script type="text/javascript" src="$sbRoot/js/ajaxEpSubtitles.js?$sbPID"></script>
+<script type="text/javascript" src="$sbRoot/js/lib/jquery.collapser.min.js?$sbPID"></script>
 <script type="text/javascript" charset="utf-8">
 <!--
 \$(document).ready(function(){
@@ -265,14 +267,16 @@
                 #if $show.rls_ignore_words:
                     <tr><td class="showLegend">Ignored Words: </td><td>#echo $show.rls_ignore_words#</td></tr>
                 #end if
-                #if $bwl and $bwl.get_white_keywords_for("release_group"):
-                    <tr><td class="showLegend">Wanted Group#if len($bwl.get_white_keywords_for("release_group"))>1 then "s" else ""#:</td>
-                    <td>#echo ', '.join($bwl.get_white_keywords_for("release_group"))#</td>
+                #if $bwl and $bwl.whitelist:
+                    <tr>
+                        <td class="showLegend">Wanted Group#if len($bwl.whitelist)>1 then "s" else ""#:</td>
+                        <td>#echo ', '.join($bwl.whitelist)#</td>
                     </tr>
                 #end if
-                #if $bwl and $bwl.get_black_keywords_for("release_group"):
-                    <tr><td class="showLegend">Unwanted Group#if len($bwl.get_black_keywords_for("release_group"))>1 then "s" else ""#:</td>
-                    <td>#echo ', '.join($bwl.get_black_keywords_for("release_group"))#</td>
+                #if $bwl and $bwl.blacklist:
+                    <tr>
+                        <td class="showLegend">Unwanted Group#if len($bwl.blacklist)>1 then "s" else ""#:</td>
+                        <td>#echo ', '.join($bwl.blacklist)#</td>
                     </tr>
                 #end if
 
@@ -340,6 +344,7 @@
 
 <table #if not $show.is_anime then "id=\"showTable\"" else "id=\"animeTable\""# class="displayShowTable display_show" cellspacing="0" border="0" cellpadding="0">
 #set $curSeason = -1
+#set $seasonCount = 0
 #set $odd = 0
     #for $epResult in $sqlResults:
         #set $epStr = str($epResult["season"]) + "x" + str($epResult["episode"])
@@ -405,8 +410,12 @@
     </thead>            
     <tbody class="tablesorter-no-sort">      
         <tr>
-            <th class="row-seasonheader displayShowTable" colspan="13" style="width: auto;"><h3><a name="season-$epResult["season"]"></a>#if int($epResult["season"]) == 0 then "Specials" else "Season " + str($epResult["season"])#</h3></th>
-        </tr>            
+            <th class="row-seasonheader displayShowTable" colspan="13" style="width: auto;">          
+                <h3><a name="season-$epResult["season"]"></a>#if int($epResult["season"]) == 0 then "Specials" else "Season " + str($epResult["season"])#</h3>
+            </th>
+        </tr>
+    </tbody>
+    <tbody class="tablesorter-no-sort">
         <tr id="season-$epResult["season"]-cols" class="seasoncols">
             <th class="col-checkbox"><input type="checkbox" class="seasonCheck" id="$epResult["season"]" /></th>
             <th class="col-metadata">NFO</th>
@@ -428,8 +437,30 @@
     </tbody>
     <tbody class="tablesorter-no-sort">       
         <tr>
-            <th class="row-seasonheader displayShowTable" colspan="13" style="width: auto;"><h3><a name="season-$epResult["season"]"></a>#if int($epResult["season"]) == 0 then "Specials" else "Season " + str($epResult["season"])#</h3></th>
-        </tr>            
+                <th class="row-seasonheader displayShowTable" colspan="13" style="width: auto;">
+                    <div class="pull-left"> <h3><a name="season-$epResult["season"]"></a>#if int($epResult["season"]) == 0 then "Specials" else "Season " + str($epResult["season"])#</h3></div>
+                #if $sickbeard.DISPLAY_ALL_SEASONS == False and $seasonCount >= 1:
+                    <div class="pull-right">
+                        <button id="showseason-$epResult['season']" type="button" class="btn btn-xs pull-right" data-toggle="collapse" data-target="#collapseSeason-$epResult['season']"><span class="sgicon-arrowdown"></span> Show Episodes</button>
+                    <script type="text/javascript">
+                    <!--
+                        \$(function() {
+                            \$('#collapseSeason-$epResult['season']').on('hide.bs.collapse', function () {
+                                \$('#showseason-$epResult['season']').html('<span class="sgicon-arrowdown"></span> Show Episodes');
+                            })
+                            \$('#collapseSeason-$epResult['season']').on('show.bs.collapse', function () {
+                                \$('#showseason-$epResult['season']').html('<span class="sgicon-arrowup"></span> Hide Episodes');
+                            })
+                        });
+                    //-->
+                    </script>
+                    </div>                    
+                #end if
+
+            </th>
+        </tr>
+    </tbody>
+    <tbody class="tablesorter-no-sort">
         <tr id="season-$epResult["season"]-cols" class="seasoncols">
             <th class="col-checkbox"><input type="checkbox" class="seasonCheck" id="$epResult["season"]" /></th>
             <th class="col-metadata">NFO</th>
@@ -449,7 +480,12 @@
         </tr>                    
             #end if
     </tbody>
+                #set $seasonCount = $seasonCount + 1
+                #if $sickbeard.DISPLAY_ALL_SEASONS == False and $seasonCount >= 2:
+    <tbody class="collapse" id="collapseSeason-$epResult['season']">
+                #else
     <tbody>
+                #end if  
             #set $curSeason = int($epResult["season"])
         #end if
         #set $epLoc = $epResult["location"]            
diff --git a/gui/slick/interfaces/default/editShow.tmpl b/gui/slick/interfaces/default/editShow.tmpl
index ff40985259a683ceebc192deca44800e4d3e1b2d..6925945ce7adb14cfd49e37248e815c728f61610 100644
--- a/gui/slick/interfaces/default/editShow.tmpl
+++ b/gui/slick/interfaces/default/editShow.tmpl
@@ -138,20 +138,18 @@ Separate words with a comma, e.g. "word1,word2,word3"<br />
 <br />
 
 #if $show.is_anime:
-#from sickbeard.blackandwhitelist import *
-#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_blackwhitelist.tmpl")
+    #from sickbeard.blackandwhitelist import *
+    #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_blackwhitelist.tmpl")
+    <script type="text/javascript" src="$sbRoot/js/blackwhite.js?$sbPID"></script>
 #end if
 
-<input type="hidden" name="whitelist" id="whitelist"/>
-<input type="hidden" name="blacklist" id="blacklist"/>
-
 <input type="submit" id="submit" value="Submit" class="btn btn-primary" />
 </form>
 
 <script type="text/javascript" charset="utf-8">
 <!--
     var all_exceptions = new Array;
-
+    
     jQuery('#location').fileBrowser({ title: 'Select Show Location' });
    
     \$('#submit').click(function(){
@@ -162,22 +160,12 @@ Separate words with a comma, e.g. "word1,word2,word3"<br />
         });
         
         \$("#exceptions_list").val(all_exceptions);
-
-        var realvalues = [];
-
-        \$('#white option').each(function(i, selected) {
-            realvalues[i] = \$(selected).val();
-        });
-        \$("#whitelist").val(realvalues.join(","));
-
-        realvalues = [];
-        \$('#black option').each(function(i, selected) {
-            realvalues[i] = \$(selected).val();
+  
+        #if $show.is_anime:
+            generate_bwlist()
+            
+        #end if
         });
-        \$("#blacklist").val(realvalues.join(","));
-
-    });  
-   
     \$('#addSceneName').click(function() {
         var scene_ex = \$('#SceneName').val()
         var option = \$("<option>")
@@ -220,43 +208,6 @@ Separate words with a comma, e.g. "word1,word2,word3"<br />
 
     \$(this).toggle_SceneException();
 
-    \$('#removeW').click(function() {
-        return !\$('#white option:selected').remove().appendTo('#pool');
-    });
-    \$('#addW').click(function() {
-        return !\$('#pool option:selected').remove().appendTo('#white');
-    });
-    \$('#addB').click(function() {
-        return !\$('#pool option:selected').remove().appendTo('#black');
-     });
-    \$('#removeP').click(function() {
-        return !\$('#pool option:selected').remove();
-    });
-    \$('#removeB').click(function() {
-        return !\$('#black option:selected').remove().appendTo('#pool');
-    });
-
-    \$('#addToWhite').click(function() {
-        var group = \$('#addToPoolText').val()
-        if(group == "")
-            return
-        \$('#addToPoolText').val("")
-        var option = \$("<option>")
-        option.prop("value",group)
-        option.html(group)
-        return option.appendTo('#white');
-    });
-    \$('#addToBlack').click(function() {
-        var group = \$('#addToPoolText').val()
-        if(group == "")
-            return
-        \$('#addToPoolText').val("")
-        var option = \$("<option>")
-        option.prop("value",group)
-        option.html(group)
-        return option.appendTo('#black');
-    });
-
 //-->
 </script>
 </div>
diff --git a/gui/slick/interfaces/default/home.tmpl b/gui/slick/interfaces/default/home.tmpl
index 3d27ae4c744d5531d02c65516405d672e56317ff..ae4a05fc51a3fabd37db38f6da2e1ce204897a79 100644
--- a/gui/slick/interfaces/default/home.tmpl
+++ b/gui/slick/interfaces/default/home.tmpl
@@ -79,6 +79,40 @@
     type: 'numeric'
 });
 
+\$.tablesorter.addParser({ 
+    id: 'eps',
+    is: function(s) {
+        return false; 
+    },
+    format: function(s) {
+        match = s.match(/^(.*)/);
+
+        if (match == null || match[1] == "?")
+          return -10;
+
+        var nums = match[1].split(" / ");
+        if (nums[0].indexOf("+") != -1) {
+            var num_parts = nums[0].split("+");
+            nums[0] = num_parts[0];
+        }
+
+        nums[0] = parseInt(nums[0])
+        nums[1] = parseInt(nums[1])
+
+        if (nums[0] === 0)
+          return nums[1];
+
+        var finalNum = parseInt($max_download_count*nums[0]/nums[1]);
+        var pct = Math.round((nums[0]/nums[1])*100) / 1000
+        if (finalNum > 0)
+          finalNum += nums[0];
+
+        return finalNum + pct;
+    },
+    type: 'numeric'
+});
+
+
 \$(document).ready(function(){ 
 	
     \$("img#network").on('error', function(){
@@ -111,6 +145,62 @@
             #if $sickbeard.FILTER_ROW:
                 filter_columnFilters: true,
                 filter_hideFilters : true,
+                filter_saveFilters : true,
+                filter_functions : {
+                   5:function(e, n, f, i, r, c) {
+                        var test = false;
+                        var pct = Math.floor((n % 1) * 1000);
+                        if (f === '') {
+                           test = true;
+                        } else {
+                            var result = f.match(/(<|<=|>=|>)\s(\d+)/i);
+                            if (result) {
+                                if (result[1] === "<") {
+                                    if (pct < parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                } else if (result[1] === "<=") {
+                                    if (pct <= parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                } else if (result[1] === ">=") {
+                                    if (pct >= parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                } else if (result[1] === ">") {
+                                    if (pct > parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                }
+                            }
+                            
+                            var result = f.match(/(\d+)\s(-|to)\s(\d+)/i);
+                            if (result) {
+                                if ((result[2] === "-") || (result[2] === "to")) {
+                                    if ((pct >= parseInt(result[1])) && (pct <= parseInt(result[3]))) {
+                                        test = true;
+                                    }
+                                }
+                            }
+                            
+                            var result = f.match(/(=)?\s?(\d+)\s?(=)?/i);
+                            if (result) {
+                                if ((result[1] === "=") || (result[3] === "=")) {
+                                    if (parseInt(result[2]) === pct) {
+                                        test = true;
+                                    }
+                                }
+                            }
+                            
+                            if (!isNaN(parseFloat(f)) && isFinite(f)) {
+                                if (parseInt(f) === pct) {
+                                    test = true;
+                                }
+                            }                            
+                        }
+                        return test;
+                    },
+                },
             #else 
                 filter_columnFilters: false,
             #end if
@@ -146,6 +236,62 @@
             #if $sickbeard.FILTER_ROW:
                 filter_columnFilters: true,
                 filter_hideFilters : true,
+                filter_saveFilters : true,
+                filter_functions : {
+                   5:function(e, n, f, i, r, c) {
+                        var test = false;
+                        var pct = Math.floor((n % 1) * 1000);
+                        if (f === '') {
+                           test = true;
+                        } else {
+                            var result = f.match(/(<|<=|>=|>)\s(\d+)/i);
+                            if (result) {
+                                if (result[1] === "<") {
+                                    if (pct < parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                } else if (result[1] === "<=") {
+                                    if (pct <= parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                } else if (result[1] === ">=") {
+                                    if (pct >= parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                } else if (result[1] === ">") {
+                                    if (pct > parseInt(result[2])) {
+                                        test = true;
+                                    }                            
+                                }
+                            }
+                            
+                            var result = f.match(/(\d+)\s(-|to)\s(\d+)/i);
+                            if (result) {
+                                if ((result[2] === "-") || (result[2] === "to")) {
+                                    if ((pct >= parseInt(result[1])) && (pct <= parseInt(result[3]))) {
+                                        test = true;
+                                    }
+                                }
+                            }
+                            
+                            var result = f.match(/(=)?\s?(\d+)\s?(=)?/i);
+                            if (result) {
+                                if ((result[1] === "=") || (result[3] === "=")) {
+                                    if (parseInt(result[2]) === pct) {
+                                        test = true;
+                                    }
+                                }
+                            }
+                            
+                            if (!isNaN(parseFloat(f)) && isFinite(f)) {
+                                if (parseInt(f) === pct) {
+                                    test = true;
+                                }
+                            }                            
+                        }
+                        return test;
+                    },
+                },
             #else 
                 filter_columnFilters: false,
             #end if
@@ -672,7 +818,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
 		<td align="center"><span class="quality Custom">Custom</span></td>
 	#end if
 	
-		<td align="center"><span style="display: none;">$progressbar_percent</span><div id="progressbar$curShow.indexerid" style="position:relative;"></div>
+		<td align="center"><span style="display: none;">$download_stat</span><div id="progressbar$curShow.indexerid" style="position:relative;"></div>
 			<script type="text/javascript">
 			<!--
 				\$(function() {
diff --git a/gui/slick/interfaces/default/home_addShows.tmpl b/gui/slick/interfaces/default/home_addShows.tmpl
index c1784fc396d4b3ebefa4924c4210a4409fb21e68..c83159681fdd43020355d991caa4ea3557c9fcf8 100644
--- a/gui/slick/interfaces/default/home_addShows.tmpl
+++ b/gui/slick/interfaces/default/home_addShows.tmpl
@@ -29,6 +29,7 @@
     </a>
 
 	<br/><br/>
+    #if $sickbeard.USE_TRAKT == True:	
     <a href="$sbRoot/home/addShows/trendingShows/" id="btnNewShow" class="btn btn-large">
         <div class="button"><div class="icon-addtrendingshow"></div></div>
         <div class="buttontext">
@@ -38,7 +39,7 @@
     </a>
 
     <br/><br/>
-    #if $sickbeard.USE_TRAKT == True:
+
 	<a href="$sbRoot/home/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large">
         <div class="button"><div class="icon-addrecommendedshow"></div></div>
         <div class="buttontext">
@@ -60,4 +61,4 @@
 </div>
 
 
-#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl")
\ No newline at end of file
+#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl")
diff --git a/gui/slick/interfaces/default/home_newShow.tmpl b/gui/slick/interfaces/default/home_newShow.tmpl
index cd9f843070521e73667deef2f7290559cdfcb87f..ca02c5e01e68a70dce1e32e76fbe4e7f1cac6a8f 100644
--- a/gui/slick/interfaces/default/home_newShow.tmpl
+++ b/gui/slick/interfaces/default/home_newShow.tmpl
@@ -114,6 +114,7 @@
 </div>
 
 <script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
+<script type="text/javascript" src="$sbRoot/js/blackwhite.js?$sbPID"></script>
 
 </div></div></div></div>
 
diff --git a/gui/slick/interfaces/default/home_recommendedShows.tmpl b/gui/slick/interfaces/default/home_recommendedShows.tmpl
index de3afd08585ddebe2fe97985b2da5c26b2acccda..f71551ada90f25ce91ee7a5707bc4eeab5d0966b 100644
--- a/gui/slick/interfaces/default/home_recommendedShows.tmpl
+++ b/gui/slick/interfaces/default/home_recommendedShows.tmpl
@@ -1,69 +1,134 @@
-#import os.path
-#import json
 #import sickbeard
-#set global $header="Recommended Shows"
+#import datetime
+#import re
+#from sickbeard.common import *
+#from sickbeard import sbdatetime
+#from sickbeard.helpers import anon_url
+
 #set global $title="Recommended Shows"
+#set global $header="Recommended Shows"
 
-#set global $sbPath="../.."
+#set global $sbPath='..'
 
-#set global $statpath="../.."#
-#set global $topmenu="home"#
+#set global $topmenu='home'
 #import os.path
-
 #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
 
-<link rel="stylesheet" type="text/css" href="$sbRoot/css/formwizard.css?$sbPID" />
-<script type="text/javascript" src="$sbRoot/js/formwizard.js?$sbPID"></script>
-<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
 <script type="text/javascript" src="$sbRoot/js/recommendedShows.js?$sbPID"></script>
-<script type="text/javascript" src="$sbRoot/js/addShowOptions.js?$sbPID"></script>
+<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
+<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
+
+<script type="text/javascript" charset="utf-8">
+<!--
+
+\$(document).ready(function(){
+    \$( "#tabs" ).tabs({
+        collapsible: true,
+        selected: #if $sickbeard.ROOT_DIRS then '-1' else '0'#
+    });
+
+    // initialise combos for dirty page refreshes
+    \$('#showsort').val('original');
+    \$('#showsortdirection').val('asc');
+
+    var \$container = [\$('#container')];
+    jQuery.each(\$container, function (j) {
+        this.isotope({
+            itemSelector: '.trakt_show',
+            sortBy: 'original-order',
+            layoutMode: 'fitRows',
+            getSortData: {
+                name: function( itemElem ) {
+                    var name = \$( itemElem ).attr('data-name') || '';
+#if not $sickbeard.SORT_ARTICLE:
+                    name = name.replace(/^(The|A|An)\s/i, '');
+#end if
+                    return name.toLowerCase();
+                },
+                rating: '[data-rating] parseInt',
+                votes: '[data-votes] parseInt',
+            }
+        });
+    });
+
+    \$('#showsort').on( 'change', function() {
+        var sortCriteria;
+        switch (this.value) {
+            case 'original':
+                sortCriteria = 'original-order'
+                break;
+            case 'rating':
+                /* randomise, else the rating_votes can already
+                 * have sorted leaving this with nothing to do.
+                 */
+                \$('#container').isotope({sortBy: 'random'});
+                sortCriteria = 'rating';
+                break;
+            case 'rating_votes':
+                sortCriteria = ['rating', 'votes'];
+                break;
+            case 'votes':
+                sortCriteria = 'votes';
+                break;
+            default:
+                sortCriteria = 'name'
+                break;
+        }
+        \$('#container').isotope({sortBy: sortCriteria});
+    });
+
+    \$('#showsortdirection').on( 'change', function() {
+        \$('#container').isotope({sortAscending: ('asc' == this.value)});
+    });
+});
+
+//-->
+</script>
+
 
 #if $varExists('header')
-<h1 class="header">$header</h1>
+    <h1 class="header">$header</h1>
 #else
-<h1 class="title">$title</h1>
+    <h1 class="title">$title</h1>
 #end if
 
-<div id="newShowPortal">
-
-	<div id="displayText"></div>
-	<br />
-
-	<form id="recommendedShowsForm" method="post" action="$sbRoot/home/addShows/addRecommendedShow" accept-charset="utf-8"  style="width: 800px;">
-	
-		<fieldset class="sectionwrap">
-			<legend class="legendStep">Select a recommended show</legend>
-
-			<div class="stepDiv">
-				<div id="searchResults" style="height: 100%;"><br/></div>
-			</div>
-		</fieldset>
-
-		<fieldset class="sectionwrap">
-			<legend class="legendStep">Pick the parent folder</legend>
-
-			<div class="stepDiv">
-				#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_rootDirs.tmpl")
-			</div>
-		</fieldset>
-
-		<fieldset class="sectionwrap">
-			<legend class="legendStep">Customize options</legend>
-
-			<div class="stepDiv">
-				#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_addShowOptions.tmpl")
-			</div>
-		</fieldset>
-	</form>
-
-<br />
-
-<div style="width: 100%; text-align: center;">
-<input class="btn" type="button" id="addShowButton" value="Add Show" disabled="disabled" />
+<div id="tabs">
+    <ul>
+        <li><a href="#tabs-1">Manage Directories</a></li>
+        <li><a href="#tabs-2">Customize Options</a></li>
+    </ul>
+    <div id="tabs-1" class="existingtabs">
+        #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_rootDirs.tmpl")
+    </div>
+    <div id="tabs-2" class="existingtabs">
+        #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_addShowOptions.tmpl")
+    </div>
+    <br>
+
+	<span>Sort By:</span>
+	<select id="showsort" class="form-control form-control-inline input-sm">
+		<option value="name">Name</option>
+		<option value="original" selected="selected">Original</option>
+		<option value="votes">Votes</option>
+		<option value="rating">% Rating</option>
+		<option value="rating_votes">% Rating > Votes</option>
+	</select>
+
+	<span style="margin-left:12px">Sort Order:</span>
+	<select id="showsortdirection" class="form-control form-control-inline input-sm">
+		<option value="asc" selected="selected">Asc</option>
+		<option value="desc">Desc</option>
+	</select>
 </div>
 
-<script type="text/javascript" src="$sbRoot/js/rootDirs.js?$sbPID"></script>
+<br />
+<div id="trendingShows"></div>
+<br />
 
-</div>
+<script type="text/javascript" charset="utf-8">
+<!--
+window.setInterval('location.reload(true)', 600000); // Refresh every 10 minutes
+//-->
+</script>
 
-#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl")
+#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
diff --git a/gui/slick/interfaces/default/inc_addShowOptions.tmpl b/gui/slick/interfaces/default/inc_addShowOptions.tmpl
index 98a46257a93ab699aaf9a6b10e225bd870e2afa0..fd3a4917ea06d75ec91a26bd790f25ebe7811922 100644
--- a/gui/slick/interfaces/default/inc_addShowOptions.tmpl
+++ b/gui/slick/interfaces/default/inc_addShowOptions.tmpl
@@ -37,6 +37,7 @@
             </label>
         </div>
 
+#if $enable_anime_options
 		<div class="field-pair alt">
             <label for="anime" class="clearfix">
                 <span class="component-title">Anime</span>
@@ -46,6 +47,7 @@
                 </span>
             </label>
         </div>
+#end if
 
         <div class="field-pair alt">
 
@@ -72,3 +74,10 @@
                 </span>
             </label>
         </div><br>
+
+#if $enable_anime_options        
+#import sickbeard.blackandwhitelist
+#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_blackwhitelist.tmpl")
+#else
+		<input type="hidden" name="anime" id="anime" value="0" />
+#end if
\ No newline at end of file
diff --git a/gui/slick/interfaces/default/inc_blackwhitelist.tmpl b/gui/slick/interfaces/default/inc_blackwhitelist.tmpl
index 85e91bbac8b5cd61630f809fd7ea464d40b20786..0ba3c159a8be419932ccd112bd1a7ff37a83dee5 100644
--- a/gui/slick/interfaces/default/inc_blackwhitelist.tmpl
+++ b/gui/slick/interfaces/default/inc_blackwhitelist.tmpl
@@ -1,53 +1,58 @@
-<b>Fansub Groups:</b>
-	<div >
-		<p>Select your preferred fansub groups from the <b>Available Groups</b> and add them to the <b>Whitelist</b>. Add groups to the <b>Blacklist</b> to ignore them.</p>
-		<p>The <b>Whitelist</b> is checked <i>before</i> the <b>Blacklist</b>.</p>
-		<p>Groups are shown as <b>Name</b> | <b>Rating</b> | <b>Number of subbed episodes</b>.</p>
-		<p>You may also add any fansub group not listed to either list manually.</p>
-	</div>
-	<div class="bwlWrapper" id="Anime">
-	<div class="blackwhitelist all">
-		<div class="blackwhitelist anidb">
-			<div class="blackwhitelist white">
-				<span><h4>Whitelist</h4></span>
-				<select id="white" multiple="multiple" size="12">
-					#for $keyword in $whitelist:
-						<option value="$keyword">$keyword</option>
-					#end for
-				</select>
-				<br/>
-				<input class="btn" id="removeW" value="Remove" type="button"/>
-			</div>
-			<div class="blackwhitelist pool">
-				<span><h4>Available Groups</h4></span>
-				<select id="pool" multiple="multiple" size="12">
-				#for $group in $groups
-					#if $group not in $whitelist and $group['name'] not in $blacklist:
-						<option value="$group['name']">$group['name'] | $group['rating'] | $group['range']</option>
-					#end if
-				#end for
-				</select>
-				<br/>
-				<input class="btn" id="addW" value="Add to Whitelist" type="button"/>
-				<input class="btn" id="addB" value="Add to Blacklist" type="button"/>
-			</div>
-			<div class="blackwhitelist black">
-				<span><h4>Blacklist</h4></span>
-				<select id="black" multiple="multiple" size="12">
-					#for $keyword in $blacklist:
-						<option value="$keyword">$keyword</option>
-					#end for
-				</select>
-				<br/>
-				<input class="btn" id="removeB" value="Remove" type="button"/>
-			</div>
-		</div>
-		<br style="clear:both" />
-		<div class="blackwhitelist manual">
-			<input type="text" id="addToPoolText" class="form-control form-control-inline input-sm input250" />
-			<input class="btn btn-inline" type="button" value="Add to Whitelist" id="addToWhite">
-			<input class="btn btn-inline" type="button" value="Add to Blacklist" id="addToBlack">
-		</div>
-	</div>
-	<br style="clear:both" />
+<div id="blackwhitelist">
+    <input type="hidden" name="whitelist" id="whitelist"/>
+    <input type="hidden" name="blacklist" id="blacklist"/>
+
+    <b>Fansub Groups:</b>
+        <div >
+            <p>Select your preferred fansub groups from the <b>Available Groups</b> and add them to the <b>Whitelist</b>. Add groups to the <b>Blacklist</b> to ignore them.</p>
+            <p>The <b>Whitelist</b> is checked <i>before</i> the <b>Blacklist</b>.</p>
+            <p>Groups are shown as <b>Name</b> | <b>Rating</b> | <b>Number of subbed episodes</b>.</p>
+            <p>You may also add any fansub group not listed to either list manually.</p>
+        </div>
+        <div class="bwlWrapper" id="Anime">
+        <div class="blackwhitelist all">
+            <div class="blackwhitelist anidb">
+                <div class="blackwhitelist white">
+                    <span><h4>Whitelist</h4></span>
+                    <select id="white" multiple="multiple" size="12">
+                        #for $keyword in $whitelist:
+                            <option value="$keyword">$keyword</option>
+                        #end for
+                    </select>
+                    <br/>
+                    <input class="btn" id="removeW" value="Remove" type="button"/>
+                </div>
+                <div class="blackwhitelist pool">
+                    <span><h4>Available Groups</h4></span>
+                    <select id="pool" multiple="multiple" size="12">
+                    #for $group in $groups
+                        #if $group not in $whitelist and $group['name'] not in $blacklist:
+                            <option value="$group['name']">$group['name'] | $group['rating'] | $group['range']</option>
+                        #end if
+                    #end for
+                    </select>
+                    <br/>
+                    <input class="btn" id="addW" value="Add to Whitelist" type="button"/>
+                    <input class="btn" id="addB" value="Add to Blacklist" type="button"/>
+                </div>
+                <div class="blackwhitelist black">
+                    <span><h4>Blacklist</h4></span>
+                    <select id="black" multiple="multiple" size="12">
+                        #for $keyword in $blacklist:
+                            <option value="$keyword">$keyword</option>
+                        #end for
+                    </select>
+                    <br/>
+                    <input class="btn" id="removeB" value="Remove" type="button"/>
+                </div>
+            </div>
+            <br style="clear:both" />
+            <div class="blackwhitelist manual">
+                <input type="text" id="addToPoolText" class="form-control form-control-inline input-sm input250" />
+                <input class="btn btn-inline" type="button" value="Add to Whitelist" id="addToWhite">
+                <input class="btn btn-inline" type="button" value="Add to Blacklist" id="addToBlack">
+            </div>
+        </div>
+        <br style="clear:both" />
+    </div>
 </div>
\ No newline at end of file
diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl
index f46ad2d63d7d2eb66fd1eacb81262034c3cb4cef..748554c99a10c30cd38d7bfceece86a378d92ff4 100644
--- a/gui/slick/interfaces/default/inc_top.tmpl
+++ b/gui/slick/interfaces/default/inc_top.tmpl
@@ -232,7 +232,6 @@
                                 <li><a href="$sbRoot/home/status/"><i class="menu-icon-help"></i>&nbsp;Server Status</a></li>
 							</ul>
 						</li>
-			            <li id="donate"><a href="http://sr-upgrade.appspot.com" rel="noreferrer" onclick="window.open('${sickbeard.ANON_REDIRECT}' + this.href); return false;"><img src="$sbRoot/images/donate.jpg" alt="[donate]" class="navbaricon hidden-xs" /></a></li>
 					</ul>
 			#end if
 				</div><!-- /.navbar-collapse -->
diff --git a/gui/slick/interfaces/default/manage.tmpl b/gui/slick/interfaces/default/manage.tmpl
index d3fd0a5dc486e3add760aa114bd9d7de5c268404..76d7ec2711284e0442573e41ca8d2f325b39ff1a 100644
--- a/gui/slick/interfaces/default/manage.tmpl
+++ b/gui/slick/interfaces/default/manage.tmpl
@@ -9,6 +9,7 @@
 #import os.path
 #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
 
+<script type="text/javascript" src="$sbRoot/js/lib/bootbox.min.js?$sbPID"></script>
 <script type="text/javascript" charset="utf-8">
 <!--
 \$.tablesorter.addParser({
diff --git a/gui/slick/js/blackwhite.js b/gui/slick/js/blackwhite.js
new file mode 100644
index 0000000000000000000000000000000000000000..69022d02e4c7938bb765d7afeb77b13d7344b694
--- /dev/null
+++ b/gui/slick/js/blackwhite.js
@@ -0,0 +1,72 @@
+function generate_bwlist() {
+	var realvalues = [];
+
+		$('#white option').each(function(i, selected) {
+			realvalues[i] = $(selected).val();
+		});
+		$("#whitelist").val(realvalues.join(","));
+
+		realvalues = [];
+		$('#black option').each(function(i, selected) {
+			realvalues[i] = $(selected).val();
+		});
+		$("#blacklist").val(realvalues.join(","));
+};
+
+function update_bwlist (show_name) {
+		$('#pool').children().remove();
+
+        $('#blackwhitelist').show();
+        if (show_name) {
+            $.getJSON(sbRoot + '/home/fetch_releasegroups', {'show_name': show_name}, function (data) {
+            if (data['result'] == 'success') {
+                $.each(data.groups, function(i, group) {
+                    var option = $("<option>");
+                    option.attr("value", group.name);
+                    option.html(group.name + ' | ' + group.rating + ' | ' + group.range);
+                    option.appendTo('#pool');
+                });
+            }
+         });
+        }
+	};
+
+$('#removeW').click(function() {
+	!$('#white option:selected').remove().appendTo('#pool');
+});
+
+$('#addW').click(function() {
+	!$('#pool option:selected').remove().appendTo('#white');
+});
+
+$('#addB').click(function() {
+	!$('#pool option:selected').remove().appendTo('#black');
+});
+
+$('#removeP').click(function() {
+	!$('#pool option:selected').remove();
+});
+
+$('#removeB').click(function() {
+	!$('#black option:selected').remove().appendTo('#pool');
+});
+
+$('#addToWhite').click(function() {
+	var group = $('#addToPoolText').attr("value");
+	if(group == "") { return; }
+	$('#addToPoolText').attr("value", "");
+	var option = $("<option>");
+	option.attr("value",group);
+	option.html(group);
+	option.appendTo('#white');
+});
+
+$('#addToBlack').click(function() {
+	var group = $('#addToPoolText').attr("value");
+	if(group == "") { return; }
+	$('#addToPoolText').attr("value", "");
+	var option = $("<option>");
+	option.attr("value",group);
+	option.html(group);
+	option.appendTo('#black');
+});
\ No newline at end of file
diff --git a/gui/slick/js/configNotifications.js b/gui/slick/js/configNotifications.js
index f0b52ea97cb035e6755c8f8a574122d57dc43961..00713117d29f1f13caf76b270ba182c60a7eb85d 100644
--- a/gui/slick/js/configNotifications.js
+++ b/gui/slick/js/configNotifications.js
@@ -58,8 +58,8 @@ $(document).ready(function(){
 
 	$('#testPMC').click(function () {
 		var plex_host = $.trim($('#plex_host').val());
-		var plex_username = $.trim($('#plex_username').val());
-		var plex_password = $.trim($('#plex_password').val());
+		var plex_client_username = $.trim($('#plex_client_username').val());
+		var plex_client_password = $.trim($('#plex_client_password').val());
 		if (!plex_host) {
 			$('#testPMC-result').html('Please fill out the necessary fields above.');
 			$('#plex_host').addClass('warning');
@@ -68,7 +68,7 @@ $(document).ready(function(){
 		$('#plex_host').removeClass('warning');
 		$(this).prop('disabled', true);
 		$('#testPMC-result').html(loading);
-		$.get(sbRoot + '/home/testPMC', {'host': plex_host, 'username': plex_username, 'password': plex_password})
+		$.get(sbRoot + '/home/testPMC', {'host': plex_host, 'username': plex_client_username, 'password': plex_client_password})
 			.done(function (data) {
 				$('#testPMC-result').html(data);
 				$('#testPMC').prop('disabled', false);
@@ -536,5 +536,5 @@ $(document).ready(function(){
             $('.plexinfo').addClass('hide');
         }
     });
-
+    
 });
diff --git a/gui/slick/js/manageEpisodeStatuses.js b/gui/slick/js/manageEpisodeStatuses.js
index cc97068b8a7097979a1759fc9afdd7bb9aa26dba..f9d310314728e63dae482635b378e063b496d8b2 100644
--- a/gui/slick/js/manageEpisodeStatuses.js
+++ b/gui/slick/js/manageEpisodeStatuses.js
@@ -9,7 +9,7 @@ $(document).ready(function() {
         var row_class = $('#row_class').val();
         
         var row = '';
-        row += ' <tr class="'+row_class+'">';
+        row += ' <tr class="'+row_class+' show-'+indexer_id+'">';
         row += '  <td class="tableleft" align="center"><input type="checkbox" class="'+indexer_id+'-epcheck" name="'+indexer_id+'-'+season+'x'+episode+'"'+checked+'></td>';
         row += '  <td>'+season+'x'+episode+'</td>';
         row += '  <td class="tableright" style="width: 100%">'+name+'</td>';
@@ -27,21 +27,36 @@ $(document).ready(function() {
         var cur_indexer_id = $(this).attr('id');
         var checked = $('#allCheck-'+cur_indexer_id).prop('checked');
         var last_row = $('tr#'+cur_indexer_id);
+        var clicked = $(this).attr('data-clicked');
+        var action = $(this).attr('value');
         
-        $.getJSON(sbRoot+'/manage/showEpisodeStatuses',
-                  {
-                   indexer_id: cur_indexer_id,
-                   whichStatus: $('#oldStatus').val()
-                  },
-                  function (data) {
-                      $.each(data, function(season,eps){
-                          $.each(eps, function(episode, name) {
-                              //alert(season+'x'+episode+': '+name);
-                              last_row.after(make_row(cur_indexer_id, season, episode, name, checked));
+        if (!clicked)  {
+            $.getJSON(sbRoot+'/manage/showEpisodeStatuses',
+                      {
+                       indexer_id: cur_indexer_id,
+                       whichStatus: $('#oldStatus').val()
+                      },
+                      function (data) {
+                          $.each(data, function(season,eps){
+                              $.each(eps, function(episode, name) {
+                                  //alert(season+'x'+episode+': '+name);
+                                  last_row.after(make_row(cur_indexer_id, season, episode, name, checked));
+                              });
                           });
                       });
-                  });
-        $(this).hide();
+            $(this).attr('data-clicked',1);   
+            $(this).prop('value', 'Collapse');
+        } else {
+            if (action === 'Collapse') {
+                $('table tr').filter('.show-'+cur_indexer_id).hide();
+                $(this).prop('value', 'Expand');
+            }
+            else if (action === 'Expand') {
+                $('table tr').filter('.show-'+cur_indexer_id).show();
+                $(this).prop('value', 'Collapse');         
+            }
+            
+        }
     });
 
     // selects all visible episode checkboxes.
diff --git a/gui/slick/js/massUpdate.js b/gui/slick/js/massUpdate.js
index ce14fdb3df7440707d855efdc44858dbc5d53305..751ff1475ed6734d97e98d292fffd25e05fe254e 100644
--- a/gui/slick/js/massUpdate.js
+++ b/gui/slick/js/massUpdate.js
@@ -51,18 +51,42 @@ $(document).ready(function(){
       }
     });
 
-    $('.deleteCheck').each(function() {
-      if (this.checked == true) {
-        deleteArr.push($(this).attr('id').split('-')[1])
-      }
-    });
-	
 	$('.removeCheck').each(function() {
 	  if (this.checked == true) {
 		removeArr.push($(this).attr('id').split('-')[1])
 	  }
 	});
+    
+    var deleteCount = 0;
 
+    $('.deleteCheck').each(function() {
+        if (this.checked == true) {
+            deleteCount++;
+        }
+    });
+    
+    if (deleteCount >= 1) {
+        bootbox.confirm("You have selected to delete " + deleteCount + " show(s).  Are you sure you wish to cntinue? All files will be removed from your system.", function(result) {
+            if (result) {
+                $('.deleteCheck').each(function() {
+                    if (this.checked == true) {
+                        deleteArr.push($(this).attr('id').split('-')[1])
+                    }
+                });
+            }
+            if (updateArr.length+refreshArr.length+renameArr.length+subtitleArr.length+deleteArr.length+removeArr.length+metadataArr.length == 0)
+                return false;                   
+            url = 'massUpdate?toUpdate='+updateArr.join('|')+'&toRefresh='+refreshArr.join('|')+'&toRename='+renameArr.join('|')+'&toSubtitle='+subtitleArr.join('|')+'&toDelete='+deleteArr.join('|')+'&toRemove='+removeArr.join('|')+'&toMetadata='+metadataArr.join('|');
+            window.location.href = url
+        });
+    }
+    else
+    {
+        if (updateArr.length+refreshArr.length+renameArr.length+subtitleArr.length+deleteArr.length+removeArr.length+metadataArr.length == 0)
+            return false;
+        url = 'massUpdate?toUpdate='+updateArr.join('|')+'&toRefresh='+refreshArr.join('|')+'&toRename='+renameArr.join('|')+'&toSubtitle='+subtitleArr.join('|')+'&toDelete='+deleteArr.join('|')+'&toRemove='+removeArr.join('|')+'&toMetadata='+metadataArr.join('|');
+        window.location.href = url       
+    }
 /*
     $('.metadataCheck').each(function() {
       if (this.checked == true) {
@@ -70,12 +94,6 @@ $(document).ready(function(){
       }
     });
 */
-    if (updateArr.length+refreshArr.length+renameArr.length+subtitleArr.length+deleteArr.length+removeArr.length+metadataArr.length == 0)
-      return false
-
-    url = 'massUpdate?toUpdate='+updateArr.join('|')+'&toRefresh='+refreshArr.join('|')+'&toRename='+renameArr.join('|')+'&toSubtitle='+subtitleArr.join('|')+'&toDelete='+deleteArr.join('|')+'&toRemove='+removeArr.join('|')+'&toMetadata='+metadataArr.join('|')
-
-    window.location.href = url
 
   });
 
diff --git a/gui/slick/js/newShow.js b/gui/slick/js/newShow.js
index 812f178f7ee9d1c7aeda4885989d03a302a345f9..d17336c107ddea8ed368460a55a21fa4651b63ca 100644
--- a/gui/slick/js/newShow.js
+++ b/gui/slick/js/newShow.js
@@ -84,7 +84,7 @@ $(document).ready(function () {
             alert('You must choose a show to continue');
             return false;
         }
-
+        generate_bwlist()
         $('#addShowForm').submit();
     });
 
@@ -138,7 +138,7 @@ $(document).ready(function () {
         } else {
             show_name = '';
         }
-
+        update_bwlist(show_name);
         var sample_text = 'Adding show <b>' + show_name + '</b> into <b>';
 
         // if we have a root dir selected, figure out the path
@@ -194,4 +194,32 @@ $(document).ready(function () {
         }
     });
 
+    $('#anime').change (function () {
+        updateSampleText();
+        myform.loadsection(2);
+    });
+
+    function update_bwlist (show_name) {
+        $('#white').children().remove();
+        $('#black').children().remove();
+        $('#pool').children().remove();
+
+        if ($('#anime').prop('checked')) {
+            $('#blackwhitelist').show();
+            if (show_name) {
+                $.getJSON(sbRoot + '/home/fetch_releasegroups', {'show_name': show_name}, function (data) {
+                if (data['result'] == 'success') {
+                    $.each(data.groups, function(i, group) {
+                        var option = $("<option>");
+                        option.attr("value", group.name);
+                        option.html(group.name + ' | ' + group.rating + ' | ' + group.range);
+                        option.appendTo('#pool');
+                    });
+                }
+             });
+            }
+        } else {
+            $('#blackwhitelist').hide();
+        }
+    };
 });
diff --git a/gui/slick/js/recommendedShows.js b/gui/slick/js/recommendedShows.js
index c1d1e28f93f529dc398ad26a3ad3d57a7da14482..649f00bad2c32ff0c7667417728473637bc7c5dc 100644
--- a/gui/slick/js/recommendedShows.js
+++ b/gui/slick/js/recommendedShows.js
@@ -1,147 +1,21 @@
-$(document).ready(function () {
-    $('#searchResults').html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> loading recommended shows...');
-    function getRecommendedShows() {
-        $.getJSON(sbRoot + '/home/addShows/getRecommendedShows', {}, function (data) {
-            var firstResult = true;
-            var resultStr = '<fieldset>\n<legend>Recommended Shows:</legend>\n';
-            var checked = '';
-
-            if (data.results.length === 0) {
-                resultStr += '<b>No recommended shows found, update your watched shows list on trakt.tv.</b>';
-            } else {
-                $.each(data.results, function (index, obj) {
-                    if (obj[2] !== null) {
-                        if (firstResult) {
-                            checked = ' checked';
-                            firstResult = false;
-                        } else {
-                            checked = '';
-                        }
-                        
-                        var whichSeries = obj.join('|');
-                        
-                        resultStr += '<input type="radio" id="whichSeries" name="whichSeries" value="' + whichSeries + '"' + checked + ' /> ';
-                        resultStr += '<a href="' + anonURL + obj[1] + '" onclick="window.open(this.href, \'_blank\'); return false;"><b>' + obj[2] + '</b></a>';
-                        
-                        if (obj[4] !== null) {
-                            var startDate = new Date(obj[4]);
-                            var today = new Date();
-                            if (startDate > today) {
-                                resultStr += ' (will debut on ' + obj[4] + ')';
-                            } else {
-                                resultStr += ' (started on ' + obj[4] + ')';
-                            }
-                        }
-                        
-                        if (obj[0] !== null) {
-                            resultStr += ' [' + obj[0] + ']';
-                        }
-                        
-                        if (obj[3] !== null) {
-                            resultStr += '<br />' + obj[3];
-                        }
-                        
-                        resultStr += '<p /><br />';
-                    }
-                });
-                resultStr += '</ul>';
+$(document).ready(function() {
+    var trendingRequestXhr = null;
+
+    function loadContent() {
+        if (trendingRequestXhr) trendingRequestXhr.abort();
+
+        $('#trendingShows').html('<img id="searchingAnim" src="' + sbRoot + '/images/loading32' + themeSpinner + '.gif" height="32" width="32" /> Loading Recommended Shows...');
+        trendingRequestXhr = $.ajax({
+            url: sbRoot + '/home/addShows/getRecommendedShows/',
+            timeout: 60 * 1000,
+            error: function () {
+                $('#trendingShows').empty().html('Trakt timed out, refresh page to try again');
+            },
+            success: function (data) {
+                $('#trendingShows').html(data);
             }
-            resultStr += '</fieldset>';
-            $('#searchResults').html(resultStr);
-            updateSampleText();
-            myform.loadsection(0);
         });
     }
 
-    $('#addShowButton').click(function () {
-        // if they haven't picked a show don't let them submit
-        if (!$("input:radio[name='whichSeries']:checked").val() && !$("input:hidden[name='whichSeries']").val().length) {
-            alert('You must choose a show to continue');
-            return false;
-        }
-
-        $('#recommendedShowsForm').submit();
-    });
-
-    $('#qualityPreset').change(function () {
-        myform.loadsection(2);
-    });
-
-    var myform = new formtowizard({
-        formid: 'recommendedShowsForm',
-        revealfx: ['slide', 500],
-        oninit: function () {
-            getRecommendedShows();
-            updateSampleText();
-        }
-    });
-
-    function goToStep(num) {
-        $('.step').each(function () {
-            if ($.data(this, 'section') + 1 == num) {
-                $(this).click();
-            }
-        });
-    }
-
-    function updateSampleText() {
-        // if something's selected then we have some behavior to figure out
-
-        var show_name, sep_char;
-        // if they've picked a radio button then use that
-        if ($('input:radio[name=whichSeries]:checked').length) {
-            show_name = $('input:radio[name=whichSeries]:checked').val().split('|')[2];
-        } else {
-            show_name = '';
-        }
-
-        var sample_text = 'Adding show <b>' + show_name + '</b> into <b>';
-
-        // if we have a root dir selected, figure out the path
-        if ($("#rootDirs option:selected").length) {
-            var root_dir_text = $('#rootDirs option:selected').val();
-            if (root_dir_text.indexOf('/') >= 0) {
-                sep_char = '/';
-            } else if (root_dir_text.indexOf('\\') >= 0) {
-                sep_char = '\\';
-            } else {
-                sep_char = '';
-            }
-
-            if (root_dir_text.substr(sample_text.length - 1) != sep_char) {
-                root_dir_text += sep_char;
-            }
-            root_dir_text += '<i>||</i>' + sep_char;
-
-            sample_text += root_dir_text;
-        } else if ($('#fullShowPath').length && $('#fullShowPath').val().length) {
-            sample_text += $('#fullShowPath').val();
-        } else {
-            sample_text += 'unknown dir.';
-        }
-
-        sample_text += '</b>';
-
-        // if we have a show name then sanitize and use it for the dir name
-        if (show_name.length) {
-            $.get(sbRoot + '/home/addShows/sanitizeFileName', {name: show_name}, function (data) {
-                $('#displayText').html(sample_text.replace('||', data));
-            });
-        // if not then it's unknown
-        } else {
-            $('#displayText').html(sample_text.replace('||', '??'));
-        }
-
-        // also toggle the add show button
-        if (($("#rootDirs option:selected").length || ($('#fullShowPath').length && $('#fullShowPath').val().length)) &&
-            ($('input:radio[name=whichSeries]:checked').length)) {
-            $('#addShowButton').attr('disabled', false);
-        } else {
-            $('#addShowButton').attr('disabled', true);
-        }
-    }
-
-    $('#rootDirText').change(updateSampleText);
-    $('#searchResults').on('change', '#whichSeries', updateSampleText);
-
+    loadContent();
 });
diff --git a/init.upstart b/init.upstart
index 07e02588da19aa5bbedde5bba7fdcfadc1ba4d9c..f502440526dfe05e068d9f91ecbddbeb35b923aa 100755
--- a/init.upstart
+++ b/init.upstart
@@ -23,6 +23,7 @@ setgid sickbeard
 
 respawn
 respawn limit 10 5
+expect daemon
 
 script
     if [ -f /etc/default/sickbeard ]; then
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index f20e8ab68c229664d0557e75c5ddc01f0e539ef9..e5df48eb4d698ddc85bc8263652dc961a129f565 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -182,6 +182,8 @@ TRASH_REMOVE_SHOW = False
 TRASH_ROTATE_LOGS = False
 SORT_ARTICLE = False
 DEBUG = False
+DISPLAY_ALL_SEASONS = True
+
 
 USE_LISTVIEW = False
 METADATA_KODI = None
@@ -332,6 +334,9 @@ PLEX_SERVER_TOKEN = None
 PLEX_HOST = None
 PLEX_USERNAME = None
 PLEX_PASSWORD = None
+USE_PLEX_CLIENT = False
+PLEX_CLIENT_USERNAME = None
+PLEX_CLIENT_PASSWORD = None
 
 USE_GROWL = False
 GROWL_NOTIFY_ONSNATCH = False
@@ -538,7 +543,7 @@ def initialize(consoleLogging=True):
             USE_KODI, KODI_ALWAYS_ON, KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, \
             KODI_UPDATE_LIBRARY, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, BACKLOG_FREQUENCY, \
             USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_REMOVE_WATCHLIST, TRAKT_SYNC_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, traktRollingScheduler, TRAKT_USE_RECOMMENDED, TRAKT_SYNC, TRAKT_SYNC_REMOVE, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_DISABLE_SSL_VERIFY, TRAKT_TIMEOUT, TRAKT_BLACKLIST_NAME, TRAKT_USE_ROLLING_DOWNLOAD, TRAKT_ROLLING_NUM_EP, TRAKT_ROLLING_ADD_PAUSED, TRAKT_ROLLING_FREQUENCY, \
-            USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, \
+            USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, USE_PLEX_CLIENT, PLEX_CLIENT_USERNAME, PLEX_CLIENT_PASSWORD, \
             PLEX_SERVER_HOST, PLEX_SERVER_TOKEN, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \
             showUpdateScheduler, __INITIALIZED__, INDEXER_DEFAULT_LANGUAGE, EP_DEFAULT_DELETED_STATUS, LAUNCH_BROWSER, UPDATE_SHOWS_ON_START, UPDATE_SHOWS_ON_SNATCH, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, SORT_ARTICLE, showList, loadingShowList, \
             NEWZNAB_DATA, NZBS, NZBS_UID, NZBS_HASH, INDEXER_DEFAULT, INDEXER_TIMEOUT, USENET_RETENTION, TORRENT_DIR, \
@@ -572,7 +577,7 @@ def initialize(consoleLogging=True):
             AUTOPOSTPROCESSER_FREQUENCY, SHOWUPDATE_HOUR, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY, \
             ANIME_DEFAULT, NAMING_ANIME, ANIMESUPPORT, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST, \
             ANIME_SPLIT_HOME, SCENE_DEFAULT, DOWNLOAD_URL, BACKLOG_DAYS, GIT_ORG, GIT_REPO, GIT_USERNAME, GIT_PASSWORD, \
-            GIT_AUTOISSUES, DEVELOPER, gh
+            GIT_AUTOISSUES, DEVELOPER, gh, DISPLAY_ALL_SEASONS
 
         if __INITIALIZED__:
             return False
@@ -924,7 +929,10 @@ def initialize(consoleLogging=True):
         PLEX_HOST = check_setting_str(CFG, 'Plex', 'plex_host', '')
         PLEX_USERNAME = check_setting_str(CFG, 'Plex', 'plex_username', '', censor_log=True)
         PLEX_PASSWORD = check_setting_str(CFG, 'Plex', 'plex_password', '', censor_log=True)
-
+        USE_PLEX_CLIENT = bool(check_setting_int(CFG, 'Plex', 'use_plex_client', 0))
+        PLEX_CLIENT_USERNAME = check_setting_str(CFG, 'Plex', 'plex_client_username', '', censor_log=True)
+        PLEX_CLIENT_PASSWORD = check_setting_str(CFG, 'Plex', 'plex_client_password', '', censor_log=True)
+        
         USE_GROWL = bool(check_setting_int(CFG, 'Growl', 'use_growl', 0))
         GROWL_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Growl', 'growl_notify_onsnatch', 0))
         GROWL_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Growl', 'growl_notify_ondownload', 0))
@@ -1131,6 +1139,7 @@ def initialize(consoleLogging=True):
         POSTER_SORTBY = check_setting_str(CFG, 'GUI', 'poster_sortby', 'name')
         POSTER_SORTDIR = check_setting_int(CFG, 'GUI', 'poster_sortdir', 1)
         FILTER_ROW =  bool(check_setting_int(CFG, 'GUI', 'filter_row', 0))
+        DISPLAY_ALL_SEASONS = bool(check_setting_int(CFG, 'General', 'display_all_seasons', 1))
 
         # initialize NZB and TORRENT providers
         providerList = providers.makeProviderList()
@@ -1612,7 +1621,7 @@ def save_config():
     new_config['General']['web_username'] = WEB_USERNAME
     new_config['General']['web_password'] = helpers.encrypt(WEB_PASSWORD, ENCRYPTION_VERSION)
     new_config['General']['web_cookie_secret'] = WEB_COOKIE_SECRET
-    new_config['General']['web_use_gzip'] = WEB_USE_GZIP
+    new_config['General']['web_use_gzip'] = int(WEB_USE_GZIP)
     new_config['General']['download_url'] = DOWNLOAD_URL
     new_config['General']['localhost_ip'] = LOCALHOST_IP
     new_config['General']['cpu_preset'] = CPU_PRESET
@@ -1709,6 +1718,7 @@ def save_config():
     new_config['General']['calendar_unprotected'] = int(CALENDAR_UNPROTECTED)
     new_config['General']['no_restart'] = int(NO_RESTART)
     new_config['General']['developer'] = int(DEVELOPER)
+    new_config['General']['display_all_seasons'] = int(DISPLAY_ALL_SEASONS)
 
     new_config['Blackhole'] = {}
     new_config['Blackhole']['nzb_dir'] = NZB_DIR
diff --git a/sickbeard/blackandwhitelist.py b/sickbeard/blackandwhitelist.py
index 70b35cd0a373dce3b83c371857084f259350d221..510daed8e85b238988ff9cd7719965d92b5d89d3 100644
--- a/sickbeard/blackandwhitelist.py
+++ b/sickbeard/blackandwhitelist.py
@@ -16,197 +16,98 @@
 # You should have received a copy of the GNU General Public License
 # along with Sick Beard.  If not, see <http://www.gnu.org/licenses/>.
 
-from sickbeard import db, logger
+import sickbeard
+from sickbeard import db, logger, helpers
 
 class BlackAndWhiteList(object):
-    _tableBlack = "blacklist"
-    _tableWhite = "whitelist"
-    blackList = []
-    whiteList = []
-    blackDict = {}
-    whiteDict = {}
-
-    last_black_valid_result = None
-    last_white_valid_result = None
+    blacklist = []
+    whitelist = []
 
     def __init__(self, show_id):
         if not show_id:
             raise BlackWhitelistNoShowIDException()
         self.show_id = show_id
-        self.refresh()
-
-    def refresh(self):
-        logger.log(u"Building black and white list for " + str(self.show_id), logger.DEBUG)
-
-        (self.blackList, self.blackDict) = self.load_blacklist()
-        (self.whiteList, self.whiteDict) = self.load_whitelist()
-
-    def load_blacklist(self):
-        return self._load_list(self._tableBlack)
-
-    def load_whitelist(self):
-        return self._load_list(self._tableWhite)
-
-    def get_black_keywords_for(self, range):
-        if range in self.blackDict:
-            return self.blackDict[range]
-        else:
-            return []
-
-    def get_white_keywords_for(self, range):
-        if range in self.whiteDict:
-            return self.whiteDict[range]
-        else:
-            return []
-
-    def set_black_keywords(self, range, values):
-        self._del_all_black_keywords()
-        self._add_keywords(self._tableBlack, range, values)
-
-    def set_white_keywords(self, range, values):
-        self._del_all_white_keywords()
-        self._add_keywords(self._tableWhite, range, values)
-
-    def set_black_keywords_for(self, range, values):
-        self._del_all_black_keywords_for(range)
-        self._add_keywords(self._tableBlack, range, values)
-
-    def set_white_keywords_for(self, range, values):
-        self._del_all_white_keywords_for(range)
-        self._add_keywords(self._tableWhite, range, values)
-
-    def add_black_keyword(self, range, value):
-        self._add_keywords(self._tableBlack, range, [value])
-
-    def add_white_keyword(self, range, value):
-        self._add_keywords(self._tableWhite, range, [value])
-
-    def get_last_result_msg(self):
-        blackResult = whiteResult = "Untested"
-        if self.last_black_valid_result == True:
-            blackResult = "Valid"
-        elif self.last_black_valid_result == False:
-            blackResult = "Invalid"
-
-        if self.last_white_valid_result == True:
-            whiteResult = "Valid"
-        elif self.last_white_valid_result == False:
-            whiteResult = "Invalid"
-
-        return "Blacklist: " + blackResult + ", Whitelist: " + whiteResult
-
-    def _add_keywords(self, table, range, values):
+        self.load()
+
+    def load(self):
+        logger.log(u'Building black and white list for ' + str(self.show_id), logger.DEBUG)
+        self.blacklist = self._load_list('blacklist')
+        self.whitelist = self._load_list('whitelist')
+        
+    def _add_keywords(self, table, values):
         myDB = db.DBConnection()
         for value in values:
-            myDB.action("INSERT INTO " + table + " (show_id, range , keyword) VALUES (?,?,?)", [self.show_id, range, value])
-
-        self.refresh()
+            myDB.action('INSERT INTO [' + table + '] (show_id, keyword) VALUES (?,?)', [self.show_id, value]) 
 
-    def _del_all_black_keywords(self):
-        self._del_all_keywords(self._tableBlack)
+    def set_black_keywords(self, values):
+        self._del_all_keywords('blacklist')
+        self._add_keywords('blacklist', values)
+        self.blacklist = values
+        logger.log('Blacklist set to: %s' % self.blacklist, logger.DEBUG)
 
-    def _del_all_white_keywords(self):
-        self._del_all_keywords(self._tableWhite)
-
-    def _del_all_black_keywords_for(self, range):
-        self._del_all_keywords_for(self._tableBlack, range)
-
-    def _del_all_white_keywords_for(self, range):
-        self._del_all_keywords_for(self._tableWhite, range)
+    def set_white_keywords(self, values):
+        self._del_all_keywords('whitelist')
+        self._add_keywords('whitelist', values)
+        self.whitelist = values
+        logger.log('Whitelist set to: %s' % self.whitelist, logger.DEBUG)            
 
     def _del_all_keywords(self, table):
-        logger.log(u"Deleting all " + table + " keywords for " + str(self.show_id), logger.DEBUG)
-        myDB = db.DBConnection()
-        myDB.action("DELETE FROM " + table + " WHERE show_id = ?", [self.show_id])
-        self.refresh()
-
-    def _del_all_keywords_for(self, table, range):
-        logger.log(u"Deleting all " + range + " " + table + " keywords for " + str(self.show_id), logger.DEBUG)
         myDB = db.DBConnection()
-        myDB.action("DELETE FROM " + table + " WHERE show_id = ? and range = ?", [self.show_id, range])
-        self.refresh()
-
+        myDB.action('DELETE FROM [' + table + '] WHERE show_id = ?', [self.show_id])        
+        
     def _load_list(self, table):
         myDB = db.DBConnection()
-        sqlResults = myDB.select("SELECT range,keyword FROM " + table + " WHERE show_id = ? ", [self.show_id])
+        sqlResults = myDB.select('SELECT keyword FROM [' + table + '] WHERE show_id = ?', [self.show_id])
         if not sqlResults or not len(sqlResults):
-            return ([], {})
-
-        list, dict = self._build_keyword_dict(sqlResults)
-        logger.log("BWL: " + str(self.show_id) + " loaded keywords from " + table + ": " + str(dict), logger.DEBUG)
-        return list, dict
-
-    def _build_keyword_dict(self, sql_result):
-        list = []
-        dict = {}
-        for row in sql_result:
-            list.append(row["keyword"])
-            if row["range"] in dict:
-                dict[row["range"]].append(row["keyword"])
+            return []
+        groups = []
+        for result in sqlResults:
+            groups.append(result["keyword"])
+            
+        logger.log('BWL: ' + str(self.show_id) + ' loaded keywords from ' + table + ': ' + str(groups), logger.DEBUG)
+        return groups
+
+    def is_valid(self, result):
+        if not result.release_group:
+            logger.log('Failed to detect release group, invalid result', logger.DEBUG)
+            return False
+        if self.whitelist or self.blacklist:
+            if result.release_group.lower() in [x.lower() for x in self.whitelist]:
+                white_result = True
+            elif not self.whitelist:
+                white_result = True
             else:
-                dict[row["range"]] = [row["keyword"]]
-
-        return (list, dict)
-
-    def is_valid_for_black(self, haystack):
-        logger.log(u"BWL: " + str(self.show_id) + " is valid black", logger.DEBUG)
-        result = self._is_valid_for(self.blackDict, False, haystack)
-        self.last_black_valid_result = result
-        return result
-
-    def is_valid_for_white(self, haystack):
-        logger.log(u"BWL: " + str(self.show_id) + " is valid white", logger.DEBUG)
-        result = self._is_valid_for(self.whiteDict, True, haystack)
-        self.last_white_valid_result = result
-        return result
-
-    def is_valid(self, haystack):
-        return self.is_valid_for_black(haystack) and self.is_valid_for_white(haystack)
-
-    def _is_valid_for(self, list, mood, haystack):
-        if not len(list):
-            return True
-
-        results = []
-        for range in list:
-            for keyword in list[range]:
-                string = None
-                if range == "global":
-                    string = haystack.name
-                elif range in haystack.__dict__:
-                    string = haystack.__dict__[range]
-                elif not range in haystack.__dict__:
-                    results.append((not mood))
-                else:
-                    results.append(False)
+                white_result = False
+            if result.release_group.lower() in [x.lower() for x in self.blacklist]:
+                black_result = False
+            else:
+                black_result = True
 
-                if string:
-                    results.append(self._is_keyword_in_string(string, keyword) == mood)
+            logger.log('Whitelist check passed: %s. Blacklist check passed: %s' % (white_result, black_result), logger.DEBUG)
 
-        # black: mood = False
-        # white: mood = True
-        if mood in results:
-            return mood
+            if white_result and black_result:
+                 return True 
+            else:
+                 return False
         else:
-            return (not mood)
-
-    def _is_keyword_in_string(self, fromPost, fromBWList):
-        """
-        will return true if fromBWList is found in fromPost
-        for now a basic find is used
-        """
-        fromPost = fromPost.lower()
-        fromBWList = fromBWList.lower()
-        logger.log(u"BWL: " + str(self.show_id) + " comparing fromPost: " + fromPost + " vs fromBWlist: " + fromBWList, logger.DEBUG)
-        return (fromPost.find(fromBWList) >= 0)
-
-class BlackWhiteKeyword(object):
-    range = ""
-    value = []
-
-    def __init__(self, range, values):
-        self.range = range # "global" or a parser group
-        self.value = values # a list of values may contain only one item (still a list)
-
+            logger.log('No Whitelist and  Blacklist defined, check passed.', logger.DEBUG)
+            return True 
+             
 class BlackWhitelistNoShowIDException(Exception):
-    "No show_id was given"
+    'No show_id was given'
+
+def short_group_names(groups):
+    groups = groups.split(",")
+    shortGroupList = []
+    if helpers.set_up_anidb_connection():
+        for groupName in groups:
+            group = sickbeard.ADBA_CONNECTION.group(gname=groupName)
+            for line in group.datalines:
+                if line["shortname"]:
+                    shortGroupList.append(line["shortname"])
+                else:
+                    if not groupName in shortGroupList:
+                        shortGroupList.append(groupName)
+    else:
+        shortGroupList = groups
+    return shortGroupList
\ No newline at end of file
diff --git a/sickbeard/common.py b/sickbeard/common.py
index fbcf8befc7ded74ab91297fb838778b244bc8c5d..443e487d2befc069759b941a9c5c8f12d15abe04 100644
--- a/sickbeard/common.py
+++ b/sickbeard/common.py
@@ -185,7 +185,7 @@ class Quality:
 
         if anime:
             dvdOptions = checkName(["dvd", "dvdrip"], any)
-            blueRayOptions = checkName(["bluray", "blu-ray", "BD"], any)
+            blueRayOptions = checkName(["BD", "blue?-?ray"], any)
             sdOptions = checkName(["360p", "480p", "848x480", "XviD"], any)
             hdOptions = checkName(["720p", "1280x720", "960x720"], any)
             fullHD = checkName(["1080p", "1920x1080"], any)
@@ -212,10 +212,10 @@ class Quality:
             return Quality.SDTV
         elif checkName(["web.dl|webrip", "xvid|x264|h.?264"], all) and not checkName(["(720|1080)[pi]"], all):
             return Quality.SDTV
-        elif checkName(["(dvdrip|b[r|d]rip)(.ws)?.(xvid|divx|x264)"], any) and not checkName(["(720|1080)[pi]"], all):
+        elif checkName(["(dvdrip|b[rd]rip|blue?-?ray)(.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) and not checkName(
-                ["(1080)[pi]"], all):
+                ["1080[pi]"], all):
             return Quality.HDTV
         elif checkName(["720p|1080i", "hdtv", "mpeg-?2"], all) or checkName(["1080[pi].hdtv", "h.?264"], all):
             return Quality.RAWHDTV
@@ -225,9 +225,9 @@ class Quality:
             return Quality.HDWEBDL
         elif checkName(["1080p", "web.dl|webrip"], all) or checkName(["1080p", "itunes", "h.?264"], all):
             return Quality.FULLHDWEBDL
-        elif checkName(["720p", "bluray|hddvd|b[r|d]rip", "x264"], all):
+        elif checkName(["720p", "blue?-?ray|hddvd|b[rd]rip", "x264"], all):
             return Quality.HDBLURAY
-        elif checkName(["1080p", "bluray|hddvd|b[r|d]rip", "x264"], all):
+        elif checkName(["1080p", "blue?-?ray|hddvd|b[rd]rip", "x264"], all):
             return Quality.FULLHDBLURAY
         else:
             return Quality.UNKNOWN
diff --git a/sickbeard/config.py b/sickbeard/config.py
index 9a25153026e0aa6043c53cf136c9640ac9d9d325..b43aa65d330c420bfe1b57810ef655769b60b1ed 100644
--- a/sickbeard/config.py
+++ b/sickbeard/config.py
@@ -442,7 +442,14 @@ def minimax(val, default, low, high):
 ################################################################################
 def check_setting_int(config, cfg_name, item_name, def_val, silent=True):
     try:
-        my_val = int(config[cfg_name][item_name])
+        my_val = config[cfg_name][item_name]
+        if str(my_val).lower() == "true":
+            my_val = 1
+        elif str(my_val).lower() == "false":
+            my_val = 0
+
+        my_val = int(my_val)
+
         if str(my_val) == str(None):
             raise
     except:
diff --git a/sickbeard/db.py b/sickbeard/db.py
index 6f1bc32eb6dee187c1a7a3a2868f987c200b616d..c7be0a979dd08e4f0270a53e96f7954ca4450af7 100644
--- a/sickbeard/db.py
+++ b/sickbeard/db.py
@@ -59,7 +59,7 @@ class DBConnection(object):
                 self.connection = sqlite3.connect(dbFilename(self.filename, self.suffix), 20, check_same_thread=False)
                 self.connection.text_factory = self._unicode_text_factory
                 self.connection.isolation_level = None
-
+                self.connection.cursor().execute('''PRAGMA locking_mode = EXCLUSIVE''')
                 db_cons[self.filename] = self.connection
             else:
                 self.connection = db_cons[self.filename]
diff --git a/sickbeard/logger.py b/sickbeard/logger.py
index 31bb90f1f9ab68c0acf91d3d2483fec5d693399a..0359b913cc9802f9fceb5bb1d3012f3f47e2dd01 100644
--- a/sickbeard/logger.py
+++ b/sickbeard/logger.py
@@ -62,7 +62,7 @@ class CensoredFormatter(logging.Formatter, object):
             if v and len(v) > 0 and v in msg:
                 msg = msg.replace(v, len(v) * '*')
         # Needed because Newznab apikey isn't stored as key=value in a section.
-        msg = re.sub('apikey\=[^\&]*\&','apikey\=**********\&', msg)
+        msg = re.sub(r'(r|apikey|api_key)=[^&]*([&\w]?)',r'\1=**********\2', msg)
         return msg
 
 
@@ -82,6 +82,8 @@ class Logger(object):
         self.debugLogging = False
         self.logFile = None
 
+        self.submitter_running = False
+
     def initLogging(self, consoleLogging=False, fileLogging=False, debugLogging=False):
         self.logFile = self.logFile or os.path.join(sickbeard.LOG_DIR, 'sickrage.log')
         self.debugLogging = debugLogging
@@ -151,8 +153,14 @@ class Logger(object):
 
     def submit_errors(self):
         if not (sickbeard.GIT_USERNAME and sickbeard.GIT_PASSWORD and len(classes.ErrorViewer.errors) > 0):
+            self.log('Please set your GitHub username and password in the config, unable to submit issue ticket to GitHub!')
             return
 
+        if self.submitter_running:
+            return 'RUNNING'
+
+        self.submitter_running = True
+
         gh_org = sickbeard.GIT_ORG or 'SiCKRAGETV'
         gh_repo = 'sickrage-issues'
 
@@ -176,15 +184,16 @@ class Logger(object):
             # parse and submit errors to issue tracker
             for curError in sorted(classes.ErrorViewer.errors, key=lambda error: error.time, reverse=True)[:500]:
                 try:
-                    if len(str(curError.title)) > 1024:
-                        title_Error = str(curError.title)[0:1024]
-                    else:
-                        title_Error = str(curError.title)
+                    title_Error = str(curError.title)
+                    if not len(title_Error) or title_Error == 'None':
+                        title_Error = re.match("^[A-Z0-9\-\[\] :]+::\s*(.*)$", ek.ss(str(curError.message))).group(1)
+                    if len(title_Error) > 1024:
+                        title_Error = title_Error[0:1024]
                 except Exception as e:
-                    title_Error = u"Unable to extract title from error"
+                    self.log("Unable to get error title : " + sickbeard.exceptions.ex(e), ERROR)
 
                 gist = None
-                regex = "^(%s)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$" % curError.time
+                regex = "^(%s)\s+([A-Z]+)\s+([0-9A-Z\-]+)\s*(.*)$" % curError.time
                 for i, x in enumerate(log_data):
                     x = ek.ss(x)
                     match = re.match(regex, x)
@@ -219,17 +228,35 @@ class Logger(object):
                 message += u"---\n"
                 message += u"_STAFF NOTIFIED_: @SiCKRAGETV/owners @SiCKRAGETV/moderators"
 
-                issue = gh.get_organization(gh_org).get_repo(gh_repo).create_issue("[APP SUBMITTED]: " + title_Error, message)
-                if issue:
-                    self.log('Your issue ticket #%s was submitted successfully!' % issue.number)
+                title_Error = u"[APP SUBMITTED]: " + title_Error
+                reports = gh.get_organization(gh_org).get_repo(gh_repo).get_issues()
+
+                issue_found = False
+                issue_id = 0
+                for report in reports:
+                    if title_Error == report.title:
+                        comment = report.create_comment(message)
+                        if comment:
+                            issue_id = report.number
+                            self.log('Commented on existing issue #%s successfully!'  % issue_id )
+                            issue_found = True
+                        break
+
+                if not issue_found:
+                    issue = gh.get_organization(gh_org).get_repo(gh_repo).create_issue(title_Error, message)
+                    if issue:
+                        issue_id = issue.number
+                        self.log('Your issue ticket #%s was submitted successfully!'  % issue_id )
 
                 # clear error from error list
                 classes.ErrorViewer.errors.remove(curError)
 
-                return issue
+                self.submitter_running = False
+                return issue_id
         except Exception as e:
             self.log(sickbeard.exceptions.ex(e), ERROR)
 
+        self.submitter_running = False
 
 class Wrapper(object):
     instance = Logger()
@@ -245,4 +272,3 @@ class Wrapper(object):
 
 
 _globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])
-
diff --git a/sickbeard/metadata/generic.py b/sickbeard/metadata/generic.py
index 098b770424f5aacceb5e96a38d26e3942fee850e..ef31bb3abb870393ea1ffdfff8692609e590701d 100644
--- a/sickbeard/metadata/generic.py
+++ b/sickbeard/metadata/generic.py
@@ -286,14 +286,9 @@ class GenericMetadata():
                 with ek.ek(open, nfo_file_path, 'r') as xmlFileObj:
                     showXML = etree.ElementTree(file=xmlFileObj)
 
-                indexer = showXML.find('indexer')
                 indexerid = showXML.find('id')
 
                 root = showXML.getroot()
-                if indexer:
-                    indexer.text = show_obj.indexer
-                else:
-                    etree.SubElement(root, "indexer").text = str(show_obj.indexer)
 
                 if indexerid:
                     indexerid.text = show_obj.indexerid
@@ -940,11 +935,9 @@ class GenericMetadata():
 
             if showXML.findtext('title') == None \
                     or (showXML.findtext('tvdbid') == None
-                        and showXML.findtext('id') == None) \
-                            and showXML.findtext('indexer') == None:
+                        and showXML.findtext('id') == None):
                 logger.log(u"Invalid info in tvshow.nfo (missing name or id):" \
                            + str(showXML.findtext('title')) + " " \
-                           + str(showXML.findtext('indexer')) + " " \
                            + str(showXML.findtext('tvdbid')) + " " \
                            + str(showXML.findtext('id')))
                 return empty_return
@@ -964,9 +957,7 @@ class GenericMetadata():
                 return empty_return
 
             indexer = None
-            if showXML.findtext('indexer') != None:
-                indexer = int(showXML.findtext('indexer'))
-            elif showXML.find('episodeguide/url') != None:
+            if showXML.find('episodeguide/url') != None:
                 epg_url = showXML.findtext('episodeguide/url').lower()
                 if str(indexer_id) in epg_url:
                     if 'thetvdb.com' in epg_url:
diff --git a/sickbeard/metadata/kodi_12plus.py b/sickbeard/metadata/kodi_12plus.py
index 02d5b8735086229133f65fd4d269a9913b309cf2..95e9f1b5ff931311b3e0998c8100a2589704d172 100644
--- a/sickbeard/metadata/kodi_12plus.py
+++ b/sickbeard/metadata/kodi_12plus.py
@@ -164,10 +164,6 @@ class KODI_12PlusMetadata(generic.GenericMetadata):
         if getattr(myShow, 'id', None) is not None:
             indexerid.text = str(myShow["id"])
 
-        indexer = etree.SubElement(tv_node, "indexer")
-        if show_obj.indexer is not None:
-            indexer.text = str(show_obj.indexer)
-
         genre = etree.SubElement(tv_node, "genre")
         if getattr(myShow, 'genre', None) is not None:
             if isinstance(myShow["genre"], basestring):
diff --git a/sickbeard/metadata/mediabrowser.py b/sickbeard/metadata/mediabrowser.py
index f54c0c4513a1693cdfe97091227e2cdfdf3d7d86..e2d3fcd0ef3895162656e9c4c8a345646f37eca2 100644
--- a/sickbeard/metadata/mediabrowser.py
+++ b/sickbeard/metadata/mediabrowser.py
@@ -265,9 +265,6 @@ class MediaBrowserMetadata(generic.GenericMetadata):
         if getattr(myShow, 'id', None) is not None:
             indexerid.text = str(myShow['id'])
 
-        indexer = etree.SubElement(tv_node, "indexer")
-        if show_obj.indexer != None:
-            indexer.text = str(show_obj.indexer)
 
         SeriesName = etree.SubElement(tv_node, "SeriesName")
         if getattr(myShow, 'seriesname', None) is not None:
@@ -495,9 +492,6 @@ class MediaBrowserMetadata(generic.GenericMetadata):
                 indexerid = etree.SubElement(episode, "id")
                 indexerid.text = str(curEpToWrite.indexerid)
 
-                indexer = etree.SubElement(episode, "indexer")
-                indexer.text = str(curEpToWrite.show.indexer)
-
                 Persons = etree.SubElement(episode, "Persons")
 
                 Language = etree.SubElement(episode, "Language")
diff --git a/sickbeard/notifiers/emailnotify.py b/sickbeard/notifiers/emailnotify.py
index 9b21d29bd8f2390873a646cbdb36f111105aa814..8733edf1562c8b6f98444b1d95e97845a9d9341f 100644
--- a/sickbeard/notifiers/emailnotify.py
+++ b/sickbeard/notifiers/emailnotify.py
@@ -20,6 +20,7 @@
 # along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
 
 import smtplib
+import traceback
 from email.mime.multipart import MIMEMultipart
 from email.mime.text import MIMEText
 from email.utils import formatdate
diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py
index a878801854f85826193b8d74a59ced4060c9d7a7..5e39138df4fbba67c1a37c1ecd44ef0272998da3 100644
--- a/sickbeard/notifiers/plex.py
+++ b/sickbeard/notifiers/plex.py
@@ -52,12 +52,12 @@ class PLEXNotifier:
 
         # fill in omitted parameters
         if not username:
-            username = sickbeard.PLEX_USERNAME
+            username = sickbeard.PLEX_CLIENT_USERNAME
         if not password:
-            password = sickbeard.PLEX_PASSWORD
+            password = sickbeard.PLEX_CLIENT_PASSWORD
 
         if not host:
-            logger.log(u'PLEX: No host specified, check your settings', logger.ERROR)
+            logger.log(u'PLEX: No host specified, check your settings', logger.WARNING)
             return False
 
         for key in command:
@@ -110,16 +110,16 @@ class PLEXNotifier:
         """
 
         # suppress notifications if the notifier is disabled but the notify options are checked
-        if not sickbeard.USE_PLEX and not force:
+        if not sickbeard.USE_PLEX_CLIENT and not force:
             return False
 
         # fill in omitted parameters
         if not host:
             host = sickbeard.PLEX_HOST
         if not username:
-            username = sickbeard.PLEX_USERNAME
+            username = sickbeard.PLEX_CLIENT_USERNAME
         if not password:
-            password = sickbeard.PLEX_PASSWORD
+            password = sickbeard.PLEX_CLIENT_PASSWORD
 
         result = ''
         for curHost in [x.strip() for x in host.split(',')]:
@@ -184,12 +184,12 @@ class PLEXNotifier:
                 password = sickbeard.PLEX_PASSWORD
                 
             if not plex_server_token:
-                token = sickbeard.PLEX_SERVER_TOKEN
+                plex_server_token = sickbeard.PLEX_SERVER_TOKEN
             
             # if username and password were provided, fetch the auth token from plex.tv
             token_arg = ''
             if plex_server_token:
-                token_arg = '?X-Plex-Token=' + sickbeard.PLEX_SERVER_TOKEN            
+                token_arg = '?X-Plex-Token=' + plex_server_token
             elif username and password:
                 logger.log(u'PLEX: fetching plex.tv credentials for user: ' + username, logger.DEBUG)
                 req = urllib2.Request('https://plex.tv/users/sign_in.xml', data='')
@@ -224,7 +224,7 @@ class PLEXNotifier:
                     xml_tree = etree.parse(urllib.urlopen(url))
                     media_container = xml_tree.getroot()
                 except IOError, e:
-                    logger.log(u'PLEX: Error while trying to contact Plex Media Server: ' + ex(e), logger.ERROR)
+                    logger.log(u'PLEX: Error while trying to contact Plex Media Server: ' + ex(e), logger.WARNING)
                     hosts_failed.append(cur_host)
                     continue
 
@@ -260,7 +260,7 @@ class PLEXNotifier:
                     force and urllib.urlopen(url)
                     host_list.append(cur_host)
                 except Exception, e:
-                    logger.log(u'PLEX: Error updating library section for Plex Media Server: ' + ex(e), logger.ERROR)
+                    logger.log(u'PLEX: Error updating library section for Plex Media Server: ' + ex(e), logger.WARNING)
                     hosts_failed.append(cur_host)
 
             if len(hosts_match):
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index eede71fff326a0564381b4863b39490c26215555..a45a9b7184ac0e284e766ae5aff9b857a6a8153a 100644
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -341,7 +341,7 @@ class PostProcessor(object):
                 helpers.moveFile(cur_file_path, new_file_path)
                 helpers.chmodAsParent(new_file_path)
             except (IOError, OSError), e:
-                self._log("Unable to move file " + cur_file_path + " to " + new_file_path + ": " + str(e), logger.ERROR)
+                self._log("Unable to move file " + cur_file_path + " to " + new_file_path + ": " + ex(e), logger.ERROR)
                 raise
 
         self._combined_file_operation(file_path, new_path, new_base_name, associated_files, action=_int_move,
@@ -935,6 +935,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)
+                helpers.chmodAsParent(ep_obj.show._location)
+
                 # do the library update for synoindex
                 notifiers.synoindex_notifier.addFolder(ep_obj.show._location)
             except (OSError, IOError):
@@ -1076,7 +1078,7 @@ class PostProcessor(object):
         notifiers.kodi_notifier.update_library(ep_obj.show.name)
 
         # do the library update for Plex
-        notifiers.plex_notifier.update_library()
+        notifiers.plex_notifier.update_library(ep_obj)
 
         # do the library update for NMJ
         # nmj_notifier kicks off its library update when the notify_download is issued (inside notifiers)
diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py
index 884e82e389e72e7bc4e31b8084215e64f8599537..3bbab68f48389d69409053de284aad35c1e90613 100644
--- a/sickbeard/providers/btn.py
+++ b/sickbeard/providers/btn.py
@@ -146,7 +146,10 @@ class BTNProvider(generic.TorrentProvider):
             parsedJSON = server.getTorrents(apikey, params, int(results_per_page), int(offset))
 
         except jsonrpclib.jsonrpc.ProtocolError, error:
-            logger.log(u"JSON-RPC protocol error while accessing " + self.name + ": " + ex(error), logger.ERROR)
+            if error.message == 'Call Limit Exceeded':
+                logger.log(u"You have exceeded the limit of 150 calls per hour, per API key which is unique to your user account.", logger.WARNING)                
+            else:
+                logger.log(u"JSON-RPC protocol error while accessing " + self.name + ": " + ex(error), logger.ERROR)
             parsedJSON = {'api-error': ex(error)}
             return parsedJSON
 
diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py
index 5bb95a12284dc150cf424a025aa4127fa0dfc610..8fee80a5570b413f5ec5780fdea0a6304cdba3fb 100644
--- a/sickbeard/providers/generic.py
+++ b/sickbeard/providers/generic.py
@@ -277,13 +277,13 @@ class GenericProvider:
                 continue
 
             # skip if season already searched
-            if len(episodes) > 1 and searched_scene_season == epObj.scene_season:
+            if len(episodes) > 1 and search_mode == 'sponly' and searched_scene_season == epObj.scene_season:
                 continue
 
             # mark season searched for season pack searches so we can skip later on
             searched_scene_season = epObj.scene_season
 
-            if len(episodes) > 1:
+            if len(episodes) > 1 and search_mode == 'sponly':
                 # get season search results
                 for curString in self._get_season_search_strings(epObj):
                     itemList += self._doSearch(curString, search_mode, len(episodes), epObj=epObj)
diff --git a/sickbeard/providers/iptorrents.py b/sickbeard/providers/iptorrents.py
index 28a8f3c1038d54d0de6a39f96a20a62738811b53..48d793803d2ae37a12e1b23522b7210447a493ef 100644
--- a/sickbeard/providers/iptorrents.py
+++ b/sickbeard/providers/iptorrents.py
@@ -133,6 +133,8 @@ class IPTorrentsProvider(generic.TorrentProvider):
             for show_name in set(show_name_helpers.allPossibleShowNames(self.show)):
                 ep_string = show_name_helpers.sanitizeSceneName(show_name) + ' ' + \
                             sickbeard.config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season,
+                                                                  'episodenumber': ep_obj.scene_episode} + '|' + \
+                            sickbeard.config.naming_ep_type[0] % {'seasonnumber': ep_obj.scene_season,
                                                                   'episodenumber': ep_obj.scene_episode} + ' %s' % add_string
 
                 search_string['Episode'].append(re.sub('\s+', ' ', ep_string))
diff --git a/sickbeard/providers/kat.py b/sickbeard/providers/kat.py
index f07c41f44ae6f5191c21c161c66ec3fe7b0b7cfd..672f69641f4011b363501d8cd285b69ecf577064 100644
--- a/sickbeard/providers/kat.py
+++ b/sickbeard/providers/kat.py
@@ -55,7 +55,7 @@ class KATProvider(generic.TorrentProvider):
 
         self.cache = KATCache(self)
 
-        self.urls = {'base_url': 'https://kickass.to/'}
+        self.urls = {'base_url': 'https://kat.cr/'}
 
         self.url = self.urls['base_url']
 
diff --git a/sickbeard/providers/morethantv.py b/sickbeard/providers/morethantv.py
index db40d4f7070208fd266177d918a3ec568b10f62f..479362aa1f1accdd5ac1eb4267bf311a6b38f803 100755
--- a/sickbeard/providers/morethantv.py
+++ b/sickbeard/providers/morethantv.py
@@ -62,11 +62,11 @@ class MoreThanTVProvider(generic.TorrentProvider):
 
         self.cache = MoreThanTVCache(self)
 
-        self.urls = {'base_url': 'http://www.morethan.tv/',
-                'login': 'http://www.morethan.tv/login.php',
-                'detail': 'http://www.morethan.tv/torrents.php?id=%s',
-                'search': 'http://www.morethan.tv/torrents.php?tags_type=1&order_by=time&order_way=desc&action=basic&searchsubmit=1&searchstr=%s',
-                'download': 'http://www.morethan.tv/torrents.php?action=download&id=%s',
+        self.urls = {'base_url': 'https://www.morethan.tv/',
+                'login': 'https://www.morethan.tv/login.php',
+                'detail': 'https://www.morethan.tv/torrents.php?id=%s',
+                'search': 'https://www.morethan.tv/torrents.php?tags_type=1&order_by=time&order_way=desc&action=basic&searchsubmit=1&searchstr=%s',
+                'download': 'https://www.morethan.tv/torrents.php?action=download&id=%s',
                 }
 
         self.url = self.urls['base_url']
diff --git a/sickbeard/providers/scenetime.py b/sickbeard/providers/scenetime.py
index c3aaca9d341022e5f457e145904e382f3f3f0970..ff572b3ed04cdd21a09f8ae384e379f12fb2a646 100644
--- a/sickbeard/providers/scenetime.py
+++ b/sickbeard/providers/scenetime.py
@@ -178,11 +178,16 @@ class SceneTimeProvider(generic.TorrentProvider):
                             logger.log(u"The Data returned from %s does not contain any torrent links" % self.name,
                                        logger.DEBUG)
                             continue
+                       
+                        # Scenetime apparently uses different number of cells in #torrenttable based
+                        # on who you are. This works around that by extracting labels from the first
+                        # <tr> and using their index to find the correct download/seeders/leechers td.
+                        labels = [ label.get_text() for label in torrent_rows[0].find_all('td') ]
 
                         for result in torrent_rows[1:]:
                             cells = result.find_all('td')
 
-                            link = cells[1].find('a');
+                            link = cells[labels.index('Name')].find('a');
 
                             full_id = link['href'].replace('details.php?id=', '')
                             torrent_id = full_id.split("&")[0]
@@ -195,8 +200,9 @@ class SceneTimeProvider(generic.TorrentProvider):
                                 download_url = self.urls['download'] % (torrent_id, filename)
                               
                                 id = int(torrent_id)
-                                seeders = int(cells[6].get_text())
-                                leechers = int(cells[7].get_text())
+                                seeders = int(cells[labels.index('Seeders')].get_text())
+                                leechers = int(cells[labels.index('Leechers')].get_text())
+
                             except (AttributeError, TypeError):
                                 continue
 
diff --git a/sickbeard/providers/t411.py b/sickbeard/providers/t411.py
index 880efdf4fd086a069f7ea6087a984fbe5df404c2..6c0c7811759346db0d841ac86110404b23715639 100644
--- a/sickbeard/providers/t411.py
+++ b/sickbeard/providers/t411.py
@@ -182,19 +182,21 @@ class T411Provider(generic.TorrentProvider):
 
                         if len(torrents) > 0:
                             for torrent in torrents:
-
-                                torrent_name = torrent['name']
-                                torrent_id = torrent['id']
-                                torrent_download_url = (self.urls['download'] % torrent_id).encode('utf8')
-
-                                if not torrent_name or not torrent_download_url:
+                                try:
+                                    torrent_name = torrent['name']
+                                    torrent_id = torrent['id']
+                                    torrent_download_url = (self.urls['download'] % torrent_id).encode('utf8')
+
+                                    if not torrent_name or not torrent_download_url:
+                                        continue
+
+                                    item = torrent_name, torrent_download_url
+                                    logger.log(u"Found result: " + torrent_name + " (" + torrent_download_url + ")",
+                                               logger.DEBUG)
+                                    items[mode].append(item)
+                                except Exception as e:
+                                    logger.log(u"Invalid torrent data, skipping results: {0}".format(str(torrent)), logger.DEBUG)
                                     continue
-
-                                item = torrent_name, torrent_download_url
-                                logger.log(u"Found result: " + torrent_name + " (" + torrent_download_url + ")",
-                                           logger.DEBUG)
-                                items[mode].append(item)
-
                         else:
                             logger.log(u"The Data returned from " + self.name + " do not contains any torrent",
                                        logger.WARNING)
diff --git a/sickbeard/search.py b/sickbeard/search.py
index 11b40043f25ee4246b1119fd8ed026ff2bd7c3d8..ff63be776b7fc79dd4176773af69d44ab70da238 100644
--- a/sickbeard/search.py
+++ b/sickbeard/search.py
@@ -192,7 +192,6 @@ def pickBestResult(results, show, quality_list=None):
 
     logger.log(u"Picking the best result out of " + str([x.name for x in results]), logger.DEBUG)
 
-    bwl = None
     bestResult = None
 
     # find the best result for the current episode
@@ -214,11 +213,8 @@ def pickBestResult(results, show, quality_list=None):
                     continue
 
         # build the black And white list
-        if cur_result.show.is_anime:
-            if not bwl:
-                bwl = BlackAndWhiteList(cur_result.show.indexerid)
-            if not bwl.is_valid(cur_result):
-                logger.log(cur_result.name+" does not match the blacklist or the whitelist, rejecting it. Result: " + bwl.get_last_result_msg(), logger.INFO)
+        if show.is_anime:
+            if not show.release_groups.is_valid(cur_result):
                 continue
 
         logger.log("Quality of " + cur_result.name + " is " + Quality.qualityStrings[cur_result.quality])
@@ -281,9 +277,6 @@ def isFinalResult(result):
 
     show_obj = result.episodes[0].show
 
-    bwl = None
-    if show_obj.is_anime:
-        bwl = BlackAndWhiteList(show_obj.indexerid)
 
     any_qualities, best_qualities = Quality.splitQuality(show_obj.quality)
 
@@ -292,7 +285,7 @@ def isFinalResult(result):
         return False
 
     # if it does not match the shows black and white list its no good
-    elif bwl and not bwl.is_valid(result):
+    elif show_obj.is_anime and show_obj.release_groups.is_valid(result):
         return False
 
     # if there's no redownload that's higher (above) and this is the highest initial download then we're good
@@ -422,7 +415,7 @@ def searchForNeededEpisodes():
     if not didSearch:
         logger.log(
             u"No NZB/Torrent providers found or enabled in the sickrage config for daily searches. Please check your settings.",
-            logger.ERROR)
+            logger.WARNING)
 
     return foundResults.values()
 
@@ -714,6 +707,6 @@ def searchProviders(show, episodes, manualSearch=False, downCurQuality=False):
 
     if not didSearch:
         logger.log(u"No NZB/Torrent providers found or enabled in the sickrage config for backlog searches. Please check your settings.",
-                   logger.ERROR)
+                   logger.WARNING)
 
     return finalResults
diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py
index 3c46d1d67db68d9c76d7b71ce7c6897639e46d78..9858ac19dd12ab6b666cb122e58575c1881ca094 100644
--- a/sickbeard/show_name_helpers.py
+++ b/sickbeard/show_name_helpers.py
@@ -186,7 +186,6 @@ def makeSceneSeasonSearchString(show, ep_obj, extraSearchType=None):
         numseasons = int(numseasonsSQlResult[0][0])
         seasonStrings = ["S%02d" % int(ep_obj.scene_season)]
 
-    bwl = BlackAndWhiteList(show.indexerid)
     showNames = set(makeSceneShowSearchStrings(show, ep_obj.scene_season))
 
     toReturn = []
@@ -201,8 +200,8 @@ def makeSceneSeasonSearchString(show, ep_obj, extraSearchType=None):
             # for providers that don't allow multiple searches in one request we only search for Sxx style stuff
             else:
                 for cur_season in seasonStrings:
-                    if len(bwl.whiteList) > 0:
-                        for keyword in bwl.whiteList:
+                    if len(show.release_groups.whitelist) > 0:
+                        for keyword in show.release_groups.whitelist:
                             toReturn.append(keyword + '.' + curShow+ "." + cur_season)
                     else:
                         toReturn.append(curShow + "." + cur_season)
@@ -232,15 +231,14 @@ def makeSceneSearchString(show, ep_obj):
     if numseasons == 1 and not ep_obj.show.is_anime:
         epStrings = ['']
 
-    bwl = BlackAndWhiteList(ep_obj.show.indexerid)
     showNames = set(makeSceneShowSearchStrings(show, ep_obj.scene_season))
 
     toReturn = []
 
     for curShow in showNames:
         for curEpString in epStrings:
-            if len(bwl.whiteList) > 0:
-                for keyword in bwl.whiteList:
+            if len(ep_obj.show.release_groups.whitelist) > 0:
+                for keyword in ep_obj.show.release_groups.whitelist:
                     toReturn.append(keyword + '.' + curShow + '.' + curEpString)
             else:
                 toReturn.append(curShow + '.' + curEpString)
diff --git a/sickbeard/show_queue.py b/sickbeard/show_queue.py
index 5fced4e47087b2b8e7fd70fd7888c05f2834637e..0b9d85f7a22cff10374228cb65594ea1b766a3c6 100644
--- a/sickbeard/show_queue.py
+++ b/sickbeard/show_queue.py
@@ -29,6 +29,7 @@ from sickbeard import exceptions, logger, ui, db, notifiers
 from sickbeard import generic_queue
 from sickbeard import name_cache
 from sickbeard.exceptions import ex
+from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names
 
 
 class ShowQueue(generic_queue.GenericQueue):
@@ -132,13 +133,13 @@ class ShowQueue(generic_queue.GenericQueue):
         return queueItemObj
 
     def addShow(self, indexer, indexer_id, showDir, default_status=None, quality=None, flatten_folders=None,
-                lang=None, subtitles=None, anime=None, scene=None, paused=None):
+                lang=None, subtitles=None, anime=None, scene=None, paused=None, blacklist=None, whitelist=None):
 
         if lang is None:
             lang = sickbeard.INDEXER_DEFAULT_LANGUAGE
 
         queueItemObj = QueueItemAdd(indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang,
-                                    subtitles, anime, scene, paused)
+                                    subtitles, anime, scene, paused, blacklist, whitelist)
 
         self.add_item(queueItemObj)
 
@@ -195,7 +196,7 @@ class ShowQueueItem(generic_queue.QueueItem):
 
 class QueueItemAdd(ShowQueueItem):
     def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles, anime,
-                 scene, paused):
+                 scene, paused, blacklist, whitelist):
 
         self.indexer = indexer
         self.indexer_id = indexer_id
@@ -208,6 +209,8 @@ class QueueItemAdd(ShowQueueItem):
         self.anime = anime
         self.scene = scene
         self.paused = paused
+        self.blacklist = blacklist
+        self.whitelist = whitelist
 
         if sickbeard.TRAKT_USE_ROLLING_DOWNLOAD and sickbeard.USE_TRAKT:
             self.paused = sickbeard.TRAKT_ROLLING_ADD_PAUSED
@@ -307,6 +310,13 @@ class QueueItemAdd(ShowQueueItem):
             logger.log(u"Setting all episodes to the specified default status: " + str(self.show.default_ep_status))
             self.show.default_ep_status = self.default_status
 
+            if self.show.anime:
+                self.show.release_groups = BlackAndWhiteList(self.show.indexerid)
+                if self.blacklist:
+                    self.show.release_groups.set_black_keywords(self.blacklist)
+                if self.whitelist:
+                    self.show.release_groups.set_white_keywords(self.whitelist)
+                    
             # be smartish about this
             if self.show.genre and "talk show" in self.show.genre.lower():
                 self.show.air_by_date = 1
diff --git a/sickbeard/tv.py b/sickbeard/tv.py
index 216139812c8ee85cb54b03a52d86ba077213af2a..d06b1965f97d8de834ad10a37f8db6331e6a5118 100644
--- a/sickbeard/tv.py
+++ b/sickbeard/tv.py
@@ -49,6 +49,7 @@ from sickbeard import notifiers
 from sickbeard import postProcessor
 from sickbeard import subtitles
 from sickbeard import history
+from sickbeard.blackandwhitelist import BlackAndWhiteList
 from sickbeard import sbdatetime
 from sickbeard import network_timezones
 from dateutil.tz import *
@@ -113,7 +114,8 @@ class TVShow(object):
         self.isDirGood = False
         self.episodes = {}
         self.nextaired = ""
-
+        self.release_groups = None
+        
         otherShow = helpers.findCertainShow(sickbeard.showList, self.indexerid)
         if otherShow != None:
             raise exceptions.MultipleShowObjectsException("Can't create a show if it already exists")
@@ -821,6 +823,9 @@ class TVShow(object):
             if not self.imdbid:
                 self.imdbid = sqlResults[0]["imdb_id"]
 
+            if self.is_anime:
+                self.release_groups = BlackAndWhiteList(self.indexerid)  
+                
         # Get IMDb_info from database
         myDB = db.DBConnection()
         sqlResults = myDB.select("SELECT * FROM imdb_info WHERE indexer_id = ?", [self.indexerid])
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index 4ce4fdb1b48431d5cc6fb87988103cf41ee22e11..80aec23883dddd3f6425006e06366366327b68e8 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -50,7 +50,7 @@ class CheckVersion():
     def __init__(self):
         self.updater = None
         self.install_type = None        
-
+        self.amActive = False
         if sickbeard.gh:
             self.install_type = self.find_install_type()
             if self.install_type == 'git':
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index fc8bfcbd6e3139723669b6623655d828a33ec094..1e7786cc2db1cc33c6d7b7c138641737882ad91d 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -45,7 +45,7 @@ from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStri
 from sickbeard.common import SNATCHED, UNAIRED, IGNORED, ARCHIVED, WANTED, FAILED, SKIPPED
 from sickbeard.common import SD, HD720p, HD1080p
 from sickbeard.exceptions import ex
-from sickbeard.blackandwhitelist import BlackAndWhiteList
+from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names
 from sickbeard.scene_exceptions import get_scene_exceptions
 from sickbeard.browser import foldersAtPath
 from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering, get_scene_numbering_for_show, \
@@ -623,8 +623,7 @@ class CalendarHandler(BaseHandler):
         ical += 'END:VCALENDAR'
 
         return ical
-
-
+        
 @route('/ui(/?.*)')
 class UI(WebRoot):
     def __init__(self, *args, **kwargs):
@@ -898,7 +897,7 @@ class Home(WebRoot):
         self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
 
         if None is not password and set('*') == set(password):
-            password = sickbeard.PLEX_PASSWORD
+            password = sickbeard.PLEX_CLIENT_PASSWORD
 
         finalResult = ''
         for curHost in [x.strip() for x in host.split(',')]:
@@ -1265,7 +1264,7 @@ class Home(WebRoot):
 
         t.bwl = None
         if showObj.is_anime:
-            t.bwl = BlackAndWhiteList(showObj.indexerid)
+            t.bwl = showObj.release_groups
 
         t.epCounts = epCounts
         t.epCats = epCats
@@ -1307,7 +1306,7 @@ class Home(WebRoot):
     def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[],
                  flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None,
                  indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None,
-                 rls_require_words=None, anime=None, blackWords=None, whiteWords=None, blacklist=None, whitelist=None,
+                 rls_require_words=None, anime=None, blacklist=None, whitelist=None,
                  scene=None, defaultEpStatus=None):
 
         anidb_failed = False
@@ -1334,23 +1333,8 @@ class Home(WebRoot):
             t.submenu = self.HomeMenu()
 
             if showObj.is_anime:
-                bwl = BlackAndWhiteList(showObj.indexerid)
-
-                t.whiteWords = ""
-                if "global" in bwl.whiteDict:
-                    t.whiteWords = ", ".join(bwl.whiteDict["global"])
-
-                t.blackWords = ""
-                if "global" in bwl.blackDict:
-                    t.blackWords = ", ".join(bwl.blackDict["global"])
-
-                t.whitelist = []
-                if bwl.whiteDict.has_key("release_group"):
-                    t.whitelist = bwl.whiteDict["release_group"]
-
-                t.blacklist = []
-                if bwl.blackDict.has_key("release_group"):
-                    t.blacklist = bwl.blackDict["release_group"]
+                t.whitelist = showObj.release_groups.whitelist
+                t.blacklist = showObj.release_groups.blacklist
 
                 t.groups = []
                 if helpers.set_up_anidb_connection() and not anidb_failed:
@@ -1412,67 +1396,22 @@ class Home(WebRoot):
             else:
                 do_update_exceptions = True
 
-            if showObj.is_anime:
-                bwl = BlackAndWhiteList(showObj.indexerid)
-                if whitelist:
-                    whitelist = whitelist.split(",")
-                    shortWhiteList = []
-                    if helpers.set_up_anidb_connection() and not anidb_failed:
-                        try:
-                            for groupName in whitelist:
-                                group = sickbeard.ADBA_CONNECTION.group(gname=groupName)
-                                for line in group.datalines:
-                                    if line["shortname"]:
-                                        shortWhiteList.append(line["shortname"])
-                                else:
-                                    if not groupName in shortWhiteList:
-                                        shortWhiteList.append(groupName)
-                        except Exception as e:
-                            anidb_failed = True
-                            ui.notifications.error('Unable to retreive data from AniDB.')
-                            logger.log('Unable to retreive data from AniDB. Error is {0}'.format(str(e)),logger.DEBUG)
-                            shortWhiteList = whitelist
-                    else:
-                        shortWhiteList = whitelist
-                    bwl.set_white_keywords_for("release_group", shortWhiteList)
-                else:
-                    bwl.set_white_keywords_for("release_group", [])
+            with showObj.lock:
+                if anime:
+                    if not showObj.release_groups:
+                        showObj.release_groups = BlackAndWhiteList(showObj.indexerid)
 
-                if blacklist:
-                    blacklist = blacklist.split(",")
-                    shortBlacklist = []
-                    if helpers.set_up_anidb_connection() and not anidb_failed:
-                        try:
-                            for groupName in blacklist:
-                                group = sickbeard.ADBA_CONNECTION.group(gname=groupName)
-                                for line in group.datalines:
-                                    if line["shortname"]:
-                                        shortBlacklist.append(line["shortname"])
-                                else:
-                                    if not groupName in shortBlacklist:
-                                        shortBlacklist.append(groupName)
-                        except Exception as e:
-                            anidb_failed = True
-                            ui.notifications.error('Unable to retreive data from AniDB.')
-                            logger.log('Unable to retreive data from AniDB. Error is {0}'.format(str(e)),logger.DEBUG)
-                            shortBlacklist = blacklist
+                    if whitelist:
+                        shortwhitelist = short_group_names(whitelist)
+                        showObj.release_groups.set_white_keywords(shortwhitelist) 
                     else:
-                        shortBlacklist = blacklist
-                    bwl.set_black_keywords_for("release_group", shortBlacklist)
-                else:
-                    bwl.set_black_keywords_for("release_group", [])
-
-                if whiteWords:
-                    whiteWords = [x.strip() for x in whiteWords.split(",")]
-                    bwl.set_white_keywords_for("global", whiteWords)
-                else:
-                    bwl.set_white_keywords_for("global", [])
+                        showObj.release_groups.set_white_keywords([])
 
-                if blackWords:
-                    blackWords = [x.strip() for x in blackWords.split(",")]
-                    bwl.set_black_keywords_for("global", blackWords)
-                else:
-                    bwl.set_black_keywords_for("global", [])
+                    if blacklist:
+                        shortblacklist = short_group_names(blacklist)
+                        showObj.release_groups.set_black_keywords(shortblacklist)
+                    else:
+                        showObj.release_groups.set_black_keywords([])
 
         errors = []
         with showObj.lock:
@@ -1684,6 +1623,7 @@ class Home(WebRoot):
 
     def updateKODI(self, show=None):        
         showName=None
+        showObj=None
         
         if show:
             showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show))
@@ -1708,7 +1648,7 @@ class Home(WebRoot):
             return self.redirect('/home/')
 
     def updatePLEX(self):
-        if notifiers.plex_notifier.update_library():
+        if None is notifiers.plex_notifier.update_library():
             ui.notifications.message(
                 "Library update command sent to Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST)
         else:
@@ -1979,7 +1919,7 @@ class Home(WebRoot):
             showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(searchThread.show.indexerid))
             
             if not showObj:
-                logger.log('No Show Object found for show with indexerID: ' + searchThread.show.indexerid, logger.ERROR)
+                logger.log('No Show Object found for show with indexerID: ' + str(searchThread.show.indexerid), logger.ERROR)
                 return results
             
             if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem):
@@ -2170,6 +2110,15 @@ class Home(WebRoot):
         else:
             return json.dumps({'result': 'failure'})
 
+    def fetch_releasegroups(self, show_name):
+        logger.log(u'ReleaseGroups: %s' % show_name, logger.INFO)
+        if helpers.set_up_anidb_connection():
+            anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=show_name)
+            groups = anime.get_groups()
+            logger.log(u'ReleaseGroups: %s' % groups, logger.INFO)
+            return json.dumps({'result': 'success', 'groups': groups})
+
+        return json.dumps({'result': 'failure'})            
 
 @route('/home/postprocess(/?.*)')
 class HomePostProcess(Home):
@@ -2360,7 +2309,8 @@ class HomeAddShows(Home):
         """
         t = PageTemplate(rh=self, file="home_newShow.tmpl")
         t.submenu = self.HomeMenu()
-
+        t.enable_anime_options = True
+        
         indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add)
 
         if indexer_id and indexer and show_name:
@@ -2394,7 +2344,9 @@ class HomeAddShows(Home):
         t.other_shows = other_shows
         t.provided_indexer = int(indexer or sickbeard.INDEXER_DEFAULT)
         t.indexers = sickbeard.indexerApi().indexers
-
+        t.whitelist = []
+        t.blacklist = []
+        t.groups = []
         return t.respond()
 
     def recommendedShows(self):
@@ -2404,52 +2356,55 @@ class HomeAddShows(Home):
         """
         t = PageTemplate(rh=self, file="home_recommendedShows.tmpl")
         t.submenu = self.HomeMenu()
-
+        t.enable_anime_options = False
+        
         return t.respond()
 
     def getRecommendedShows(self):
-        final_results = []
-
-        logger.log(u"Getting recommended shows from Trakt.tv", logger.DEBUG)
+        t = PageTemplate(rh=self, file="trendingShows.tmpl")
+        t.submenu = self.HomeMenu()
 
+        t.trending_shows = []
+        
         trakt_api = TraktAPI(sickbeard.TRAKT_API_KEY, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD, sickbeard.TRAKT_DISABLE_SSL_VERIFY, sickbeard.TRAKT_TIMEOUT)
 
         try:
-            recommendedlist = trakt_api.traktRequest("recommendations/shows?extended=full,images")
-
-            if recommendedlist:
-                indexers = ['tvdb', 'tvrage']
-                map(final_results.append, (
-                    [int(show['ids'][indexers[sickbeard.TRAKT_DEFAULT_INDEXER - 1]]),
-                     'http://www.trakt.tv/shows/%s' % show['ids']['slug'], show['title'],
-                     show['overview'],
-                     None if show['first_aired'] is None else dateutil_parser.parse(show['first_aired']).strftime(sickbeard.DATE_PRESET)]
-                    for show in recommendedlist if not helpers.findCertainShow(sickbeard.showList, [
-                    int(show['ids'][indexers[sickbeard.TRAKT_DEFAULT_INDEXER - 1]])])))
-        except traktException as e:
-            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
+            not_liked_show = ""
+            if sickbeard.TRAKT_BLACKLIST_NAME is not None and sickbeard.TRAKT_BLACKLIST_NAME:
+                not_liked_show = trakt_api.traktRequest("users/" + sickbeard.TRAKT_USERNAME + "/lists/" + sickbeard.TRAKT_BLACKLIST_NAME + "/items") or []
+            else:
+                logger.log(u"trending blacklist name is empty", logger.DEBUG)
 
-        return json.dumps({'results': final_results})
+            shows = trakt_api.traktRequest("recommendations/shows?extended=full,images") or []
 
-    def addRecommendedShow(self, whichSeries=None, indexerLang=None, rootDir=None, defaultStatus=None,
-                           anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
-                           fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None,
-                           scene=None):
+            library_shows = trakt_api.traktRequest("sync/collection/shows?extended=full") or []
+            
+            for show_detail in shows:
+                show = {}
+                show['show']=show_detail
+                try:
+                    tvdb_id = int(show['show']['ids']['tvdb'])
+                    tvrage_id = int(show['show']['ids']['tvrage'] or 0)
+                    if not helpers.findCertainShow(sickbeard.showList, [tvdb_id, tvrage_id]):
+                        if show['show']['ids']['tvdb'] not in (lshow['show']['ids']['tvdb'] for lshow in library_shows):
+                            if not_liked_show:
+                                if show['show']['ids']['tvdb'] not in (show['show']['ids']['tvdb'] for show in not_liked_show if show['type'] == 'show'):	
+                                    t.trending_shows += [show]
+                            else:
+                                t.trending_shows += [show]
 
-        indexer = 1
-        indexer_name = sickbeard.indexerApi(int(indexer)).name
-        show_url = whichSeries.split('|')[1]
-        indexer_id = whichSeries.split('|')[0]
-        show_name = whichSeries.split('|')[2]
+                except exceptions.MultipleShowObjectsException:
+                    continue
 
-        if indexerLang is None:
-            indexerLang = sickbeard.INDEXER_DEFAULT_LANGUAGE
+            if sickbeard.TRAKT_BLACKLIST_NAME != '':
+                t.blacklist = True
+            else:
+                t.blacklist = False
 
-        return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]),
-                               indexerLang, rootDir,
-                               defaultStatus,
-                               anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows,
-                               skipShow, providedIndexer, anime, scene)
+        except traktException as e:
+            logger.log(u"Could not connect to Trakt service: %s" % ex(e), logger.WARNING)
+
+        return t.respond()
 
     def trendingShows(self):
         """
@@ -2458,6 +2413,7 @@ class HomeAddShows(Home):
         """
         t = PageTemplate(rh=self, file="home_trendingShows.tmpl")
         t.submenu = self.HomeMenu()
+        t.enable_anime_options = False
 
         return t.respond()
 
@@ -2489,11 +2445,10 @@ class HomeAddShows(Home):
                 try:
                     tvdb_id = int(show['show']['ids']['tvdb'])
                     tvrage_id = int(show['show']['ids']['tvrage'] or 0)
-                    if not helpers.findCertainShow(sickbeard.showList,
-                                                   [tvdb_id, tvrage_id]):
+                    if not helpers.findCertainShow(sickbeard.showList, [tvdb_id, tvrage_id]):
                         if show['show']['ids']['tvdb'] not in (lshow['show']['ids']['tvdb'] for lshow in library_shows):
                             if not_liked_show:
-                                if show['show']['ids']['tvdb'] not in (show['show']['ids']['tvdb'] for show in not_liked_show if show['type'] == 'show'):	
+                                if show['show']['ids']['tvdb'] not in (show['show']['ids']['tvdb'] for show in not_liked_show if show['type'] == 'show'):
                                     t.trending_shows += [show]
                             else:
                                 t.trending_shows += [show]
@@ -2536,7 +2491,8 @@ class HomeAddShows(Home):
         """
         t = PageTemplate(rh=self, file="home_addExistingShow.tmpl")
         t.submenu = self.HomeMenu()
-
+        t.enable_anime_options = False
+        
         return t.respond()
 
     def addTraktShow(self, indexer_id, showName):
@@ -2577,7 +2533,7 @@ class HomeAddShows(Home):
     def addNewShow(self, whichSeries=None, indexerLang=None, rootDir=None, defaultStatus=None,
                    anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
                    fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None,
-                   scene=None):
+                   scene=None, blacklist=None, whitelist=None):
         """
         Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are
         provided then it forwards back to newShow, if not it goes to /home.
@@ -2665,6 +2621,11 @@ class HomeAddShows(Home):
         flatten_folders = config.checkbox_to_value(flatten_folders)
         subtitles = config.checkbox_to_value(subtitles)
 
+        if whitelist:
+            whitelist = short_group_names(whitelist)
+        if blacklist:
+            blacklist = short_group_names(blacklist)
+        
         if not anyQualities:
             anyQualities = []
         if not bestQualities:
@@ -2678,7 +2639,7 @@ class HomeAddShows(Home):
         # add the show
         sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality,
                                                     flatten_folders, indexerLang, subtitles, anime,
-                                                    scene)
+                                                    scene, None, blacklist, whitelist)
         ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
 
         return finishAddShow()
@@ -3712,7 +3673,7 @@ class ConfigGeneral(Config):
                     calendar_unprotected=None, debug=None, no_restart=None, coming_eps_missed_range=None,
                     filter_row=None, fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=None,
                     indexer_timeout=None, download_url=None, rootDir=None, theme_name=None,
-                    git_reset=None, git_username=None, git_password=None, git_autoissues=None):
+                    git_reset=None, git_username=None, git_password=None, git_autoissues=None, display_all_seasons=None):
 
         results = []
 
@@ -3750,7 +3711,8 @@ class ConfigGeneral(Config):
         sickbeard.DEBUG = config.checkbox_to_value(debug)
         # sickbeard.LOG_DIR is set in config.change_LOG_DIR()
         sickbeard.COMING_EPS_MISSED_RANGE = config.to_int(coming_eps_missed_range,default=7)
-
+        sickbeard.DISPLAY_ALL_SEASONS = config.checkbox_to_value(display_all_seasons)
+        
         sickbeard.WEB_PORT = config.to_int(web_port)
         sickbeard.WEB_IPV6 = config.checkbox_to_value(web_ipv6)
         # sickbeard.WEB_LOG is set in config.change_LOG_DIR()
@@ -4639,6 +4601,7 @@ class ConfigNotifications(Config):
                           use_plex=None, plex_notify_onsnatch=None, plex_notify_ondownload=None,
                           plex_notify_onsubtitledownload=None, plex_update_library=None,
                           plex_server_host=None, plex_server_token=None, plex_host=None, plex_username=None, plex_password=None,
+                          use_plex_client=None, plex_client_username=None, plex_client_password=None,
                           use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None,
                           growl_notify_onsubtitledownload=None, growl_host=None, growl_password=None,
                           use_freemobile=None, freemobile_notify_onsnatch=None, freemobile_notify_ondownload=None,
@@ -4703,7 +4666,10 @@ class ConfigNotifications(Config):
         sickbeard.PLEX_SERVER_TOKEN = config.clean_host(plex_server_token)
         sickbeard.PLEX_USERNAME = plex_username
         sickbeard.PLEX_PASSWORD = plex_password
-
+        sickbeard.USE_PLEX_CLIENT = config.checkbox_to_value(use_plex)
+        sickbeard.PLEX_CLIENT_USERNAME = plex_username
+        sickbeard.PLEX_CLIENT_PASSWORD = plex_password
+        
         sickbeard.USE_GROWL = config.checkbox_to_value(use_growl)
         sickbeard.GROWL_NOTIFY_ONSNATCH = config.checkbox_to_value(growl_notify_onsnatch)
         sickbeard.GROWL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(growl_notify_ondownload)
@@ -5064,8 +5030,10 @@ class ErrorLogs(WebRoot):
             ui.notifications.error("Missing information", "Please set your GitHub username and password in the config.")
             logger.log(u'Please set your GitHub username and password in the config, unable to submit issue ticket to GitHub!')
         else:
-            issue = logger.submit_errors()
-            if issue:
-                ui.notifications.message('Your issue ticket #%s was submitted successfully!' % issue.number)
+            issue_id = logger.submit_errors()
+            if issue_id == 'RUNNING':
+                ui.notifications.message('Issue submitter is running, please wait for it to complete')
+            elif issue_id: 
+                ui.notifications.message('Your issue ticket #%s was submitted successfully!' % issue_id)
 
         return self.redirect("/errorlogs/")
diff --git a/tests/all_tests.py b/tests/all_tests.py
old mode 100644
new mode 100755
index 28bdeb635e2d7481970ff7caf681cd6d6a42683f..05d8cf4ec3e7f1fd8bb23312ee680b92f055dbe0
--- a/tests/all_tests.py
+++ b/tests/all_tests.py
@@ -20,12 +20,18 @@
 
 import glob
 import unittest
-import sys
+import sys, os.path
+
+tests_dir=os.path.abspath(__file__)[:-len(os.path.basename(__file__))]
+
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 class AllTests(unittest.TestCase):
+    blacklist = [tests_dir + 'all_tests.py',tests_dir + 'issue_submitter_tests.py']
     def setUp(self):
-        self.test_file_strings = [ x for x in glob.glob('*_tests.py') if not x in __file__]
-        self.module_strings = [file_string[0:len(file_string) - 3] for file_string in self.test_file_strings]
+        self.test_file_strings = [ x for x in glob.glob(tests_dir + '*_tests.py') if not x in self.blacklist ]
+        self.module_strings = [file_string[len(tests_dir):len(file_string) - 3] for file_string in self.test_file_strings]
         self.suites = [unittest.defaultTestLoader.loadTestsFromName(file_string) for file_string in self.module_strings]
         self.testSuite = unittest.TestSuite(self.suites)
 
@@ -34,11 +40,11 @@ class AllTests(unittest.TestCase):
         print "STARTING - ALL TESTS"
         print "=================="
         for includedfiles in self.test_file_strings:
-            print "- " + includedfiles
+            print "- " + includedfiles[len(tests_dir):-3]
 
         text_runner = unittest.TextTestRunner().run(self.testSuite)
         if not text_runner.wasSuccessful():
             sys.exit(-1)
 
 if __name__ == "__main__":
-    unittest.main()
\ No newline at end of file
+    unittest.main()
diff --git a/tests/common_tests.py b/tests/common_tests.py
index 19b4632e6794272ed9aec61eaf0e5fa5992011eb..5c01a8143d94046054ff152abdd4cc1374709eab 100644
--- a/tests/common_tests.py
+++ b/tests/common_tests.py
@@ -3,8 +3,8 @@ import unittest
 import sys
 import os.path
 
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 from sickbeard import common
 
diff --git a/tests/config_tests.py b/tests/config_tests.py
index 42a8fecdd5b6536401d2287333c56bf108aba7dd..7b0e61cf3b28b370d2465d1e77df38a44d8785f9 100644
--- a/tests/config_tests.py
+++ b/tests/config_tests.py
@@ -2,10 +2,11 @@ import unittest
 
 import sys
 import os.path
-sys.path.append(os.path.abspath('..'))
 
-from sickbeard import config
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
+from sickbeard import config
 
 class QualityTests(unittest.TestCase):
 
diff --git a/tests/db_tests.py b/tests/db_tests.py
index 4802ad97b28caf035d30a7f270cda9c234a1257e..74383f8f5d5d28639636684053212b256fb72e0c 100644
--- a/tests/db_tests.py
+++ b/tests/db_tests.py
@@ -17,6 +17,10 @@
 # You should have received a copy of the GNU General Public License
 # along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
 
+import sys, os.path
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
 import unittest
 import test_lib as test
 import threading
diff --git a/tests/encoding_tests.py b/tests/encoding_tests.py
index 40089b27074b695c8280eb870df8de90f33d5077..0084458178aca381571b3267fbea3fcba044a7dc 100644
--- a/tests/encoding_tests.py
+++ b/tests/encoding_tests.py
@@ -3,8 +3,8 @@ import locale
 import unittest
 import sys, os.path
 
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 import sickbeard
 from sickbeard import encodingKludge as ek
@@ -41,4 +41,4 @@ if __name__ == "__main__":
     print "=================="
     print "######################################################################"
     suite = unittest.TestLoader().loadTestsFromTestCase(EncodingTests)
-    unittest.TextTestRunner(verbosity=2).run(suite)
\ No newline at end of file
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/feedparser_tests.py b/tests/feedparser_tests.py
index dfb203cc8912a6efcde24ebef17cfd80860de3bd..d7505d9419e37720714460957d4e131e0fa84e48 100644
--- a/tests/feedparser_tests.py
+++ b/tests/feedparser_tests.py
@@ -1,10 +1,9 @@
 import unittest
-import sys
-import os.path
+import sys, os.path
 import test_lib as test
 
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 from sickbeard.rssfeeds import RSSFeeds
 
@@ -25,4 +24,4 @@ if __name__ == "__main__":
     testresults = unittest.TextTestRunner(verbosity=2).run(suite)
 
     # Return 0 if successful, 1 if there was a failure
-    sys.exit(not testresults.wasSuccessful())
\ No newline at end of file
+    sys.exit(not testresults.wasSuccessful())
diff --git a/tests/issue_submitter_tests.py b/tests/issue_submitter_tests.py
index bb3e29dad5c217e0b1149759c885a2dd6444658e..0337a212e745315622410861bd1e14fd4953699d 100644
--- a/tests/issue_submitter_tests.py
+++ b/tests/issue_submitter_tests.py
@@ -23,8 +23,8 @@ import unittest
 import sys, os.path
 from configobj import ConfigObj
 
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 import sickbeard
 import test_lib as test
@@ -37,13 +37,16 @@ def error():
         sickbeard.logger.submit_errors()
         raise
 
+
 class IssueSubmitterBasicTests(unittest.TestCase):
     def test_submitter(self):
         self.assertRaises(Exception, error)
 
+
 if __name__ == "__main__":
     print "=================="
     print "STARTING - ISSUE SUBMITTER TESTS"
     print "=================="
     print "######################################################################"
-    suite = unittest.TestLoader().loadTestsFromTestCase(IssueSubmitterBasicTests)
\ No newline at end of file
+    suite = unittest.TestLoader().loadTestsFromTestCase(IssueSubmitterBasicTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/name_parser_tests.py b/tests/name_parser_tests.py
index 1522a66e47a79d38a25e86e151ac18cc26df20f2..df73515edfb2812ab77ef96abdd875a500213469 100644
--- a/tests/name_parser_tests.py
+++ b/tests/name_parser_tests.py
@@ -3,9 +3,8 @@ import unittest
 import test_lib as test
 
 import sys, os.path
-
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 from sickbeard.name_parser import parser
 
@@ -349,6 +348,7 @@ class BasicTests(test.SickbeardTestDBCase):
 if __name__ == '__main__':
     if len(sys.argv) > 1:
         suite = unittest.TestLoader().loadTestsFromName('name_parser_tests.BasicTests.test_'+sys.argv[1])
+        unittest.TextTestRunner(verbosity=2).run(suite)
     else:
         suite = unittest.TestLoader().loadTestsFromTestCase(BasicTests)
     unittest.TextTestRunner(verbosity=2).run(suite)
@@ -360,4 +360,4 @@ if __name__ == '__main__':
     unittest.TextTestRunner(verbosity=2).run(suite)
 
     suite = unittest.TestLoader().loadTestsFromTestCase(FailureCaseTests)
-    unittest.TextTestRunner(verbosity=2).run(suite)
\ No newline at end of file
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/pp_tests.py b/tests/pp_tests.py
index 6f2435d20d9c179961ef85be35ce43d2590f67ac..5db84328002b015589da2a5909a40bbdf183523d 100644
--- a/tests/pp_tests.py
+++ b/tests/pp_tests.py
@@ -23,6 +23,8 @@ import unittest
 import test_lib as test
 
 import sys, os.path
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 from sickbeard.postProcessor import PostProcessor
 import sickbeard
diff --git a/tests/scene_helpers_tests.py b/tests/scene_helpers_tests.py
index e5f4de192436f2bf9947f62c07914e5ea362e493..b044894f4dcc489304c959235fbfa5c9b2cf810c 100644
--- a/tests/scene_helpers_tests.py
+++ b/tests/scene_helpers_tests.py
@@ -2,9 +2,8 @@ import unittest
 import test_lib as test
 
 import sys, os.path
-
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 from sickbeard import show_name_helpers, scene_exceptions, common, name_cache
 
diff --git a/tests/snatch_tests.py b/tests/snatch_tests.py
index d54479f57c0f56c5079ecbcbc452c07a0fe60d85..ad06483beb3ca882651fac60fd8fce5ae7240ad4 100644
--- a/tests/snatch_tests.py
+++ b/tests/snatch_tests.py
@@ -23,6 +23,8 @@ import unittest
 import test_lib as test
 
 import sys, os.path
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 import sickbeard.search as search
 import sickbeard
diff --git a/tests/test_lib.py b/tests/test_lib.py
index 65c6e406a692099a2057c7aa935debbf1925a135..f504816a7a1320024e5694d5b44b62ae438c031c 100644
--- a/tests/test_lib.py
+++ b/tests/test_lib.py
@@ -20,15 +20,12 @@
 from __future__ import with_statement
 
 import unittest
-
 import sqlite3
-
-import sys
-import os.path
 from configobj import ConfigObj
 
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+import sys, os.path
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 import sickbeard
 
@@ -201,26 +198,19 @@ def tearDown_test_db():
     """Deletes the test db
         although this seams not to work on my system it leaves me with an zero kb file
     """
-    # uncomment next line so leave the db intact between test and at the end
-    # return False
 
-    try:
-        if os.path.exists(os.path.join(TESTDIR, TESTDBNAME)):
-            os.remove(os.path.join(TESTDIR, TESTDBNAME))
-    except:
-        pass
+    #uncomment next line so leave the db intact between test and at the end
+    #return False
 
-    try:
-        if os.path.exists(os.path.join(TESTDIR, TESTCACHEDBNAME)):
-            os.remove(os.path.join(TESTDIR, TESTCACHEDBNAME))
-    except:
-        pass
+    for current_db in [ TESTDBNAME, TESTCACHEDBNAME, TESTFAILEDDBNAME ]:
+        for file_name in [ os.path.join(TESTDIR, current_db), os.path.join(TESTDIR, current_db + '-journal') ]:
+            if os.path.exists(file_name):
+                try:
+                    os.remove(file_name)
+                except (IOError, OSError) as e:
+                    print 'ERROR: Failed to remove ' + file_name
+                    print ex(e)
 
-    try:
-        if os.path.exists(os.path.join(TESTDIR, TESTFAILEDDBNAME)):
-            os.remove(os.path.join(TESTDIR, TESTFAILEDDBNAME))
-    except:
-        pass
 
 def setUp_test_episode_file():
     if not os.path.exists(FILEDIR):
@@ -248,7 +238,6 @@ def tearDown_test_show_dir():
     if os.path.exists(SHOWDIR):
         shutil.rmtree(SHOWDIR)
 
-tearDown_test_db()
 
 if __name__ == '__main__':
     print "=================="
diff --git a/tests/torrent_tests.py b/tests/torrent_tests.py
index f6c84afa5824fbbb0fe0af9e8dd559fc8a85b533..cf8e39c994e5abf5a117fb00d63e4ce1e171b74b 100644
--- a/tests/torrent_tests.py
+++ b/tests/torrent_tests.py
@@ -20,10 +20,10 @@
 from __future__ import with_statement
 
 import unittest
-import sys, os.path
 
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+import sys, os.path
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 import urlparse
 import test_lib as test
@@ -72,7 +72,8 @@ class TorrentBasicTests(test.SickbeardTestDBCase):
 
 if __name__ == "__main__":
     print "=================="
-    print "STARTING - XEM Scene Numbering TESTS"
+    print "STARTING - Torrent Basic TESTS"
     print "=================="
     print "######################################################################"
-    suite = unittest.TestLoader().loadTestsFromTestCase(TorrentBasicTests)
\ No newline at end of file
+    suite = unittest.TestLoader().loadTestsFromTestCase(TorrentBasicTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/tv_tests.py b/tests/tv_tests.py
index ea8f26ea868beda0b944cd2e5643d8945f660f6d..9b130d17cd019ab79fe66990c550cc9d2ce13f5e 100644
--- a/tests/tv_tests.py
+++ b/tests/tv_tests.py
@@ -20,6 +20,10 @@
 import unittest
 import test_lib as test
 
+import sys, os.path
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
 import sickbeard
 from sickbeard.tv import TVEpisode, TVShow
 
diff --git a/tests/xem_tests.py b/tests/xem_tests.py
index a2b2657986fa264326f7077763397d6f92f45d35..a26477f09a0f8e9952f94ff045cdb7f44ac66bb3 100644
--- a/tests/xem_tests.py
+++ b/tests/xem_tests.py
@@ -20,12 +20,12 @@
 from __future__ import with_statement
 
 import unittest
-import sys, os.path
 import datetime
 import re
 
-sys.path.append(os.path.abspath('..'))
-sys.path.append(os.path.abspath('../lib'))
+import sys, os.path
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')))
+sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 
 import test_lib as test
 import sickbeard
@@ -84,4 +84,5 @@ if __name__ == "__main__":
     print "STARTING - XEM Scene Numbering TESTS"
     print "=================="
     print "######################################################################"
-    suite = unittest.TestLoader().loadTestsFromTestCase(XEMBasicTests)
\ No newline at end of file
+    suite = unittest.TestLoader().loadTestsFromTestCase(XEMBasicTests)
+    unittest.TextTestRunner(verbosity=2).run(suite)