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"> </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"> </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"> </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"> </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> 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)