diff --git a/CHANGES.md b/CHANGES.md index b847ed4970fe481ebfea621bc04ee3b2cf48141d..00e17cb6c1ef661861aa477843d67e6a15778c33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,19 +1,60 @@ +### 4.0.17 (2015-04-19) + +[full changelog](https://github.com/SiCKRAGETV/SickRage/compare/v4.0.16...v4.0.17) + +* libnotify: Use gir-notify instead of pynotify. +* Fix for xml_declaration unexpected keyword in python 2.6. +* Fix UnboundLocalError exception can be thrown at piList.append(curQueueItem). +* Fix Auto determine indexer when indexer tag not present in nfo. +* Fix editShow: Select the default indexer language instead of forcing en. +* Fix Catch emailnotify smtp error. +* Fix Change log timeout errors to WARNING in requests provider clients. +* Fix Ignore OSError when obtaining the size of a path in get_size. +* New Add RSS to EZTV provider. +* New SceneTime provider. +* Fix iptorrent: Add missing import. +* New Add Pause Button on displayShow page. +* Fix Episode Thumbnail url when show is in DVD Order. +* Fix Catch all errors from Kodi notify. +* Fix Add user agent to RARBG when login. +* New Use better logging for get_size exceptions. +* New Added qbittorrent support. +* Fix rarbg: remove urllib.quote. +* Update PNotify to latest version. +* Fix Plex Notification. +* New added the option to use Plex Token vs Username / Password. +* Fix PP Case Sensitive associated files check. +* Fix unknown quality being accepted as a valid quality in PP. +* Fix added notification of unsupported multiepisode torrent. +* Fix proper: saveSearch broken for download propers. +* New Add logline to show found associated files. +* New Add a log line containing connecting IP on login. +* Fix eztv provider. +* Fix Title of None for Errors submitted through UI. +* New Stay on Show Page after "Update Kodi". +* Fix NoneType object has no attribute getOverview. +* New Manage Rolling Download on unpause from submenu button. +* Fix provide a more detailed http code error. +* Fix Show subtitle service unavailable as info. +* New Column Selection for displayShow. +* Fix Update IPTorrents search URL. + ### 4.0.16 (2015-04-12) [full changelog](https://github.com/SiCKRAGETV/SickRage/compare/v4.0.15...v4.0.16) -* New Feature: Added option to add a filter row on main show page (enabled in general settings - interface) -* New Feature: added scheduling status page -* Fixed EZTV rss blank result -* Fixed RARBG tokend -* Fixed Home page filter: change to allow to use parsed data for active(yes/no) -* Fixed OldTPB: Check if the returned results ar proper|repack -* Don't display paused show in backlogOverview -* Trakt Sync by Episode not by Show -* Added gzip setting in config.ini -* Added TRAKTROLLING to filter in viewlog -* Redone Scheduler -* Replace fuzzy images on Add Show ( Add Trending) +* New Feature: Added option to add a filter row on main show page (enabled in general settings - interface) +* New Feature: added scheduling status page +* Fixed EZTV rss blank result +* Fixed RARBG tokend +* Fixed Home page filter: change to allow to use parsed data for active(yes/no) +* Fixed OldTPB: Check if the returned results ar proper|repack +* Don't display paused show in backlogOverview +* Trakt Sync by Episode not by Show +* Added gzip setting in config.ini +* Added TRAKTROLLING to filter in viewlog +* Redone Scheduler +* Replace fuzzy images on Add Show ( Add Trending) ### 4.0.15 (2015-04-05) diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index 95467041f27778b1f1ba2ff1e62d3ea9a5217582..6a320fef293220ca1dda1ca6b9e6114a7366c03b 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -1192,6 +1192,29 @@ span.snatched b { opacity: 0.4; } +.displayShowTable { + table-layout: auto; + width: 100%; + border-collapse: collapse; + border-spacing: 0; + text-align: center; + border: none; + empty-cells: show; + color: #000 !important; +} + +.displayShowTable.display_show { + clear:both +} + +.displayShowTable th.row-seasonheader { + border: none !important; + background-color: #222 !important; + color: #fff !important; + padding-top: 15px !important; + text-align: left !important; +} + .sickbeardTable { table-layout: auto; width: 100%; diff --git a/gui/slick/css/lib/pnotify.custom.min.css b/gui/slick/css/lib/pnotify.custom.min.css index 1d0fa42eca7d01a07cc39d456075f7ce0c76c997..8f2d3eb74d4cf473f30609b7d6711947e16b47d1 100644 Binary files a/gui/slick/css/lib/pnotify.custom.min.css and b/gui/slick/css/lib/pnotify.custom.min.css differ diff --git a/gui/slick/css/light.css b/gui/slick/css/light.css index 8df3fe1d64aacf8d1fc65bcdca2ee36d32c58ce7..1c49d9714af641a0a414e4b1eb8d2fc6a20c2197 100644 --- a/gui/slick/css/light.css +++ b/gui/slick/css/light.css @@ -1167,6 +1167,29 @@ span.snatched b { opacity: 0.4; } +.displayShowTable { + table-layout: auto; + width: 100%; + border-collapse: collapse; + border-spacing: 0; + text-align: center; + border: none; + empty-cells: show; + color: #000 !important; +} + +.displayShowTable.display_show { + clear:both +} + +.displayShowTable th.row-seasonheader { + border: none !important; + background-color: #fff !important; + color: #000 !important; + padding-top: 15px !important; + text-align: left !important; +} + .sickbeardTable { table-layout: auto; width: 100%; diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index f4d250dc5c8ffd73d8350ed3549768d7ded9d381..4216d0c4ac1585481bdbde9cac540c47e4bf9113 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -1193,6 +1193,29 @@ span.snatched b { opacity: 0.4; } +.displayShowTable { + table-layout: auto; + width: 100%; + border-collapse: collapse; + border-spacing: 0; + text-align: center; + border: none; + empty-cells: show; + color: #000 !important; +} + +.displayShowTable.display_show { + clear:both +} + +.displayShowTable th.row-seasonheader { + border: none !important; + background-color: #222 !important; + color: #fff !important; + padding-top: 15px !important; + text-align: left !important; +} + .sickbeardTable { table-layout: auto; width: 100%; diff --git a/gui/slick/images/providers/scenetime.png b/gui/slick/images/providers/scenetime.png new file mode 100644 index 0000000000000000000000000000000000000000..32e729817493ca9411fc78e698a8b9c37a081780 Binary files /dev/null and b/gui/slick/images/providers/scenetime.png differ diff --git a/gui/slick/interfaces/default/config_general.tmpl b/gui/slick/interfaces/default/config_general.tmpl index e4651ce30aa262b1cb2560b3fc16242296476a06..3948f456e21749e879ee65fb39d716018da2ba6f 100644 --- a/gui/slick/interfaces/default/config_general.tmpl +++ b/gui/slick/interfaces/default/config_general.tmpl @@ -299,20 +299,11 @@ <label for="filter_row"> <span class="component-title">Filter Row</span> <span class="component-desc"> - <input type="checkbox" name="filter_row" id="display_filesize" #if $sickbeard.FILTER_ROW == True then 'checked="checked"' else ''#/> + <input type="checkbox" name="filter_row" id="filter_row" #if $sickbeard.FILTER_ROW == True then 'checked="checked"' else ''#/> <p>Add a filter row to the show display on the home page</p> </span> </label> </div> - <div class="field-pair"> - <label for="display_filesize"> - <span class="component-title">Display Filesizes</span> - <span class="component-desc"> - <input type="checkbox" name="display_filesize" id="display_filesize" #if $sickbeard.DISPLAY_FILESIZE == True then 'checked="checked"' else ''#/> - <p>display filesizes for downloaded episodes on show page</p> - </span> - </label> - </div> <div class="field-pair"> <label for="coming_eps_missed_range"> <span class="component-title">Missed episodes range</span> @@ -774,4 +765,4 @@ //--> </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') \ No newline at end of file diff --git a/gui/slick/interfaces/default/config_notifications.tmpl b/gui/slick/interfaces/default/config_notifications.tmpl index 3fb21751c7305d6e4c29eb30956b2c06d3784230..5b82eb6f80418f4d54a018a137acb373086d87f2 100644 --- a/gui/slick/interfaces/default/config_notifications.tmpl +++ b/gui/slick/interfaces/default/config_notifications.tmpl @@ -175,6 +175,73 @@ </div> <div id="content_use_plex"> + <div class="field-pair"> + <label for="plex_server_token"> + <span class="component-title">Plex Media Server Auth Token</span> + <input type="text" name="plex_server_token" id="plex_server_token" value="$sickbeard.PLEX_SERVER_TOKEN" class="form-control input-sm input250" /> + </label> + <label> + <span class="component-title"> </span> + <span class="component-desc">Auth Token used by plex</span> + </label> + <label> + <span class="component-title"> </span> + <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: 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="field-pair"> <label for="plex_notify_onsnatch"> <span class="component-title">Notify on snatch</span> @@ -202,78 +269,25 @@ </span> </label> </div> - <div class="field-pair"> - <label for="plex_update_library"> - <span class="component-title">Update library</span> - <span class="component-desc"> - <input type="checkbox" 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 class="field-pair"> - <label for="plex_server_host"> - <span class="component-title">Plex Media Server IP:Port</span> - <input type="text" name="plex_server_host" id="plex_server_host" value="$sickbeard.PLEX_SERVER_HOST" class="form-control input-sm input250" /> - </label> - <label> - <span class="component-title"> </span> - <span class="component-desc">host running Plex Media Server (eg. 192.168.1.100:32400)</span> - </label> - </div> - <div class="field-pair"> - <label for="plex_server_token"> - <span class="component-title">Plex Media Server Auth Token</span> - <input type="text" name="plex_server_token" id="plex_server_token" value="$sickbeard.PLEX_SERVER_TOKEN" class="form-control input-sm input250" /> - </label> - <label> - <span class="component-title"> </span> - <span class="component-desc">Auth Token used by plex</span> - </label> - <label> - <span class="component-title"> </span> - <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="field-pair"> <label for="plex_host"> <span class="component-title">Plex Client IP:Port</span> - <input type="text" name="plex_host" id="plex_host" value="$sickbeard.PLEX_HOST" class="form-control input-sm input350" /> - </label> - <label> - <span class="component-title"> </span> - <span class="component-desc">host running Plex Client (eg. 192.168.1.100:3000)</span> - </label> - <label> - <span class="component-title"> </span> - <span class="component-desc">(multiple host strings must be separated by commas)</span> - </label> - </div> - <div class="field-pair"> - <label for="plex_username"> - <span class="component-title">Plex Client username</span> - <input type="text" name="plex_username" id="plex_username" value="$sickbeard.PLEX_USERNAME" class="form-control input-sm input250" /> - </label> - <label> - <span class="component-title"> </span> - <span class="component-desc">username for your Plex client API (blank for none)</span> + <span class="component-desc"> + <input type="text" name="plex_host" id="plex_host" value="$sickbeard.PLEX_HOST" class="form-control input-sm input350" /> + <div class="clear-left"> + <p>one or more hosts running Plex client<br />(eg. 192.168.1.100:3000, 192.168.1.101:3000)</p> + </div> + </span> </label> </div> + <div class="field-pair"> - <label for="plex_password"> - <span class="component-title">Plex Client password</span> - <input type="password" name="plex_password" id="plex_password" value="$sickbeard.PLEX_PASSWORD" class="form-control input-sm input250" /> - </label> - <label> - <span class="component-title"> </span> - <span class="component-desc">password for your Plex client API (blank for none)</span> - </label> + <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 class="testNotification" id="testPLEX-result">Click below to test.</div> - <input class="btn" type="button" value="Test Plex Client" id="testPLEX" /> - <input type="submit" class="config_submitter btn" value="Save Changes" /> </div><!-- /content_use_plex --> - </fieldset> </div><!-- /plex component-group --> diff --git a/gui/slick/interfaces/default/config_search.tmpl b/gui/slick/interfaces/default/config_search.tmpl index 2c745ab0d6baa96d22c8737f44480376da7a329b..4b2a8648aac37e51ad2db537b06272dd66dd83a4 100755 --- a/gui/slick/interfaces/default/config_search.tmpl +++ b/gui/slick/interfaces/default/config_search.tmpl @@ -448,8 +448,8 @@ <span class="component-title">Send .torrent files to:</span> <span class="component-desc"> <select name="torrent_method" id="torrent_method" class="form-control input-sm"> -#set $torrent_method_text = {'blackhole': "Black hole", 'utorrent': "uTorrent", 'transmission': "Transmission", 'deluge': "Deluge", 'download_station': "Synology DS", 'rtorrent': "rTorrent"} -#for $curAction in ('blackhole', 'utorrent', 'transmission', 'deluge', 'download_station', 'rtorrent'): +#set $torrent_method_text = {'blackhole': "Black hole", 'utorrent': "uTorrent", 'transmission': "Transmission", 'deluge': "Deluge", 'download_station': "Synology DS", 'rtorrent': "rTorrent", 'qbittorrent': "qbittorrent"} +#for $curAction in ('blackhole', 'utorrent', 'transmission', 'deluge', 'download_station', 'rtorrent', 'qbittorrent'): #set $selected = $html_selected if $sickbeard.TORRENT_METHOD == $curAction else '' <option value="$curAction"$selected>$torrent_method_text[$curAction]</option> #end for diff --git a/gui/slick/interfaces/default/displayShow.tmpl b/gui/slick/interfaces/default/displayShow.tmpl index 3ed05330b56b85fe151a1061e3bc978fb5767836..358389403416a46f0658d96a6e7e579b9c9443f9 100644 --- a/gui/slick/interfaces/default/displayShow.tmpl +++ b/gui/slick/interfaces/default/displayShow.tmpl @@ -7,6 +7,7 @@ #import os.path, os #import datetime #import urllib +#import ntpath #set global $title=$show.name ##set global $header = '<a></a>' % @@ -47,487 +48,533 @@ }); }); #end raw - - \$.fn.generateStars = function() { - return this.each(function(i,e){\$(e).html(\$('<span/>').width(\$(e).text()*12));}); - }; + + \$.fn.generateStars = function() { + return this.each(function(i,e){\$(e).html(\$('<span/>').width(\$(e).text()*12));}); + }; + + \$('.imdbstars').generateStars(); + + + + #if $show.is_anime: + \$("#animeTable").tablesorter({ + #else: + \$("#showTable").tablesorter({ + #end if + widgets: ['saveSort', 'stickyHeaders', 'columnSelector'], + widgetOptions : { + columnSelector_saveColumns: true, + columnSelector_layout : '<br/><label><input type="checkbox">{name}</label>', + columnSelector_mediaquery: false, + columnSelector_cssChecked : 'checked' + }, + }); - \$('.imdbstars').generateStars(); - + + + \$('#popover') + .popover({ + placement: 'bottom', + html: true, // required if content has HTML + content: '<div id="popover-target"></div>' + }) + // bootstrap popover event triggered when the popover opens + .on('shown.bs.popover', function () { + #if $show.is_anime: + \$.tablesorter.columnSelector.attachTo( \$('#animeTable'), '#popover-target'); + #else: + \$.tablesorter.columnSelector.attachTo( \$('#showTable'), '#popover-target'); + #end if + }); }); //--> </script> - <div class="pull-left form-inline"> - Change Show: - <div class="navShow"><img id="prevShow" src="$sbRoot/images/prev.png" alt="<<" title="Prev Show" /></div> - <select id="pickShow" class="form-control form-control-inline input-sm"> - #for $curShowList in $sortedShowLists: - #set $curShowType = $curShowList[0] - #set $curShowList = $curShowList[1] - - #if len($sortedShowLists) > 1: - <optgroup label="$curShowType"> - #end if - #for $curShow in $curShowList: - <option value="$curShow.indexerid" #if $curShow == $show then "selected=\"selected\"" else ""#>$curShow.name</option> - #end for - #if len($sortedShowLists) > 1: - </optgroup> - #end if - #end for - </select> - <div class="navShow"><img id="nextShow" src="$sbRoot/images/next.png" alt=">>" title="Next Show" /></div> - </div> - - <div class="clearfix"></div> - - <div id="showtitle" data-showname="$show.name"> - <h1 class="title" id="scene_exception_$show.indexerid">$show.name</h1> - </div> - - - #if $seasonResults: - ##There is a special/season_0?## - #if int($seasonResults[-1]["season"]) == 0: - #set $season_special = 1 - #else: - #set $season_special = 0 - #end if - - #if not $sickbeard.DISPLAY_SHOW_SPECIALS and $season_special: - $seasonResults.pop(-1) - #end if - - <span class="h2footer displayspecials pull-right"> - #if $season_special: - Display Specials: - #if sickbeard.DISPLAY_SHOW_SPECIALS: - <a class="inner" href="$sbRoot/toggleDisplayShowSpecials/?show=$show.indexerid">Hide</a> - #else: - <a class="inner" href="$sbRoot/toggleDisplayShowSpecials/?show=$show.indexerid">Show</a> - #end if - #end if - </span> - - <div class="h2footer pull-right"> - <span> - #if (len($seasonResults) > 14): - <select id="seasonJump" class="form-control input-sm" style="position: relative; top: -4px;"> - <option value="jump">Jump to Season</option> - #for $seasonNum in $seasonResults: - <option value="#season-$seasonNum["season"]">#if int($seasonNum["season"]) == 0 then "Specials" else "Season " + str($seasonNum["season"])#</option> - #end for - </select> - #else: - Season: - #for $seasonNum in $seasonResults: - #if int($seasonNum["season"]) == 0: - <a href="#season-$seasonNum["season"]">Specials</a> - #else: - <a href="#season-$seasonNum["season"]">${str($seasonNum["season"])}</a> - #end if - #if $seasonNum != $seasonResults[-1]: - <span class="separator">|</span> - #end if - #end for - #end if - </span> - - #end if - </div> - - <div class="clearfix"></div> + <div class="pull-left form-inline"> + Change Show: + <div class="navShow"><img id="prevShow" src="$sbRoot/images/prev.png" alt="<<" title="Prev Show" /></div> + <select id="pickShow" class="form-control form-control-inline input-sm"> + #for $curShowList in $sortedShowLists: + #set $curShowType = $curShowList[0] + #set $curShowList = $curShowList[1] + + #if len($sortedShowLists) > 1: + <optgroup label="$curShowType"> + #end if + #for $curShow in $curShowList: + <option value="$curShow.indexerid" #if $curShow == $show then "selected=\"selected\"" else ""#>$curShow.name</option> + #end for + #if len($sortedShowLists) > 1: + </optgroup> + #end if + #end for + </select> + <div class="navShow"><img id="nextShow" src="$sbRoot/images/next.png" alt=">>" title="Next Show" /></div> + </div> + + <div class="clearfix"></div> + + <div id="showtitle" data-showname="$show.name"> + <h1 class="title" id="scene_exception_$show.indexerid">$show.name</h1> + </div> + + + #if $seasonResults: + ##There is a special/season_0?## + #if int($seasonResults[-1]["season"]) == 0: + #set $season_special = 1 + #else: + #set $season_special = 0 + #end if + + #if not $sickbeard.DISPLAY_SHOW_SPECIALS and $season_special: + $seasonResults.pop(-1) + #end if + + <span class="h2footer displayspecials pull-right"> + #if $season_special: + Display Specials: + #if sickbeard.DISPLAY_SHOW_SPECIALS: + <a class="inner" href="$sbRoot/toggleDisplayShowSpecials/?show=$show.indexerid">Hide</a> + #else: + <a class="inner" href="$sbRoot/toggleDisplayShowSpecials/?show=$show.indexerid">Show</a> + #end if + #end if + </span> + + <div class="h2footer pull-right"> + <span> + #if (len($seasonResults) > 14): + <select id="seasonJump" class="form-control input-sm" style="position: relative; top: -4px;"> + <option value="jump">Jump to Season</option> + #for $seasonNum in $seasonResults: + <option value="#season-$seasonNum["season"]">#if int($seasonNum["season"]) == 0 then "Specials" else "Season " + str($seasonNum["season"])#</option> + #end for + </select> + #else: + Season: + #for $seasonNum in $seasonResults: + #if int($seasonNum["season"]) == 0: + <a href="#season-$seasonNum["season"]">Specials</a> + #else: + <a href="#season-$seasonNum["season"]">${str($seasonNum["season"])}</a> + #end if + #if $seasonNum != $seasonResults[-1]: + <span class="separator">|</span> + #end if + #end for + #end if + </span> + + #end if + </div> + + <div class="clearfix"></div> #if $show_message: - <div class="alert alert-info"> - $show_message - </div> + <div class="alert alert-info"> + $show_message + </div> #end if - - <div id="container"> - <div id="posterCol"> - <a href="$sbRoot/showPoster/?show=$show.indexerid&which=poster" rel="dialog" title="View Poster for $show.name"><img src="$sbRoot/showPoster/?show=$show.indexerid&which=poster_thumb" class="tvshowImg" alt=""/></a> - </div> - - <div id="showCol"> - - <div id="showinfo"> + + <div id="container"> + <div id="posterCol"> + <a href="$sbRoot/showPoster/?show=$show.indexerid&which=poster" rel="dialog" title="View Poster for $show.name"><img src="$sbRoot/showPoster/?show=$show.indexerid&which=poster_thumb" class="tvshowImg" alt=""/></a> + </div> + + <div id="showCol"> + + <div id="showinfo"> #if 'rating' in $show.imdb_info: #set $rating_tip = str($show.imdb_info['rating']) + " / 10" + " Stars" + "<br />" + str($show.imdb_info['votes']) + " Votes" - <span class="imdbstars" qtip-content="$rating_tip">$show.imdb_info['rating']</span> + <span class="imdbstars" qtip-content="$rating_tip">$show.imdb_info['rating']</span> #end if - + #set $_show = $show #if not $show.imdbid - <span>($show.startyear) - $show.runtime minutes - </span> + <span>($show.startyear) - $show.runtime minutes - </span> #else #if 'country_codes' in $show.imdb_info: #for $country in $show.imdb_info['country_codes'].split('|') - <img src="$sbRoot/images/blank.png" class="country-flag flag-${$country}" width="16" height="11" style="margin-left: 3px; vertical-align:middle;" /> + <img src="$sbRoot/images/blank.png" class="country-flag flag-${$country}" width="16" height="11" style="margin-left: 3px; vertical-align:middle;" /> #end for #end if #if 'year' in $show.imdb_info: - <span>($show.imdb_info['year']) - $show.imdb_info['runtimes'] minutes - </span> + <span>($show.imdb_info['year']) - $show.imdb_info['runtimes'] minutes - </span> #end if - <a href="<%= anon_url('http://www.imdb.com/title/', _show.imdbid) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;" title="http://www.imdb.com/title/$show.imdbid"><img alt="[imdb]" height="16" width="16" src="$sbRoot/images/imdb.png" style="margin-top: -1px; vertical-align:middle;"/></a> + <a href="<%= anon_url('http://www.imdb.com/title/', _show.imdbid) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;" title="http://www.imdb.com/title/$show.imdbid"><img alt="[imdb]" height="16" width="16" src="$sbRoot/images/imdb.png" style="margin-top: -1px; vertical-align:middle;"/></a> #end if - <a href="<%= anon_url(sickbeard.indexerApi(_show.indexer).config['show_url'], _show.indexerid) %>" onclick="window.open(this.href, '_blank'); return false;" title="$sickbeard.indexerApi($show.indexer).config["show_url"]$show.indexerid"><img alt="$sickbeard.indexerApi($show.indexer).name" height="16" width="16" src="$sbRoot/images/$sickbeard.indexerApi($show.indexer).config["icon"] "style="margin-top: -1px; vertical-align:middle;"/></a> + <a href="<%= anon_url(sickbeard.indexerApi(_show.indexer).config['show_url'], _show.indexerid) %>" onclick="window.open(this.href, '_blank'); return false;" title="$sickbeard.indexerApi($show.indexer).config["show_url"]$show.indexerid"><img alt="$sickbeard.indexerApi($show.indexer).name" height="16" width="16" src="$sbRoot/images/$sickbeard.indexerApi($show.indexer).config["icon"] "style="margin-top: -1px; vertical-align:middle;"/></a> #if $xem_numbering or $xem_absolute_numbering: - <a href="<%= anon_url('http://thexem.de/search?q=', _show.name) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;" title="http://thexem.de/search?q-$show.name"><img alt="[xem]" height="16" width="16" src="$sbRoot/images/xem.png" style="margin-top: -1px; vertical-align:middle;"/></a> + <a href="<%= anon_url('http://thexem.de/search?q=', _show.name) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;" title="http://thexem.de/search?q-$show.name"><img alt="[xem]" height="16" width="16" src="$sbRoot/images/xem.png" style="margin-top: -1px; vertical-align:middle;"/></a> #end if - </div> - - <div id="tags"> - <ul class="tags"> - #if not $show.imdbid - #if $show.genre: - #for $genre in $show.genre[1:-1].split('|') - <a href="<%= anon_url('http://trakt.tv/shows/popular/', genre.lower()) %>" target="_blank" title="View other popular $genre shows on trakt.tv."><li>$genre</li></a> - #end for - #end if - #end if - #if 'year' in $show.imdb_info: - #for $imdbgenre in $show.imdb_info['genres'].replace('Sci-Fi','Science-Fiction').split('|') - <a href="<%= anon_url('http://trakt.tv/shows/popular/', imdbgenre.lower()) %>" target="_blank" title="View other popular $imdbgenre shows on trakt.tv."><li>$imdbgenre</li></a> - #end for - #end if - </ul> - </div> - - <div id="summary"> - <table class="summaryTable pull-left"> - #set $anyQualities, $bestQualities = $Quality.splitQuality(int($show.quality)) - <tr><td class="showLegend">Quality: </td><td> - #if $show.quality in $qualityPresets: - <span class="quality $qualityPresetStrings[$show.quality]">$qualityPresetStrings[$show.quality]</span> - #else: - #if $anyQualities: - <i>Initial:</i> <%=", ".join([Quality.qualityStrings[x] for x in sorted(anyQualities)])%> #if $bestQualities then " </br> " else ""# - #end if - #if $bestQualities: - <i>Replace with:</i> <%=", ".join([Quality.qualityStrings[x] for x in sorted(bestQualities)])%> - #end if - #end if - - #if $show.network and $show.airs: - <tr><td class="showLegend">Originally Airs: </td><td>$show.airs #if not $network_timezones.test_timeformat($show.airs) then " <font color='#FF0000'><b>(invalid Timeformat)</b></font> " else ""# on $show.network</td></tr> - #else if $show.network: - <tr><td class="showLegend">Originally Airs: </td><td>$show.network</td></tr> - #else if $show.airs: - <tr><td class="showLegend">Originally Airs: </td><td>>$show.airs #if not $network_timezones.test_timeformat($show.airs) then " <font color='#FF0000'><b>(invalid Timeformat)</b></font> " else ""#</td></tr> - #end if - <tr><td class="showLegend">Show Status: </td><td>$show.status</td></tr> - <tr><td class="showLegend">Default EP Status: </td><td>$statusStrings[$show.default_ep_status]</td></tr> - #if $showLoc[1]: - <tr><td class="showLegend">Location: </td><td>$showLoc[0]</td></tr> - #else: - <tr><td class="showLegend"><span style="color: red;">Location: </span></td><td><span style="color: red;">$showLoc[0]</span> (dir is missing)</td></tr> - #end if - <tr><td class="showLegend">Scene Name:</td><td>#if $show.exceptions then $exceptions_string else $show.name#</td></tr> - - #if $show.rls_require_words: - <tr><td class="showLegend">Required Words: </td><td>#echo $show.rls_require_words#</td></tr> - #end if - #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> - </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> - </tr> - #end if - - <tr><td class="showLegend">Size:</td><td>$sickbeard.helpers.pretty_filesize(sickbeard.helpers.get_size($showLoc[0]))</td></tr> - - </table> - - <table style="width:180px; float: right; vertical-align: middle; height: 100%;"> - <tr><td class="showLegend">Info Language:</td><td><img src="$sbRoot/images/flags/${show.lang}.png" width="16" height="11" alt="$show.lang" title="$show.lang" /></td></tr> - #if $sickbeard.USE_SUBTITLES - <tr><td class="showLegend">Subtitles: </td><td><img src="$sbRoot/images/#if int($show.subtitles) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - #end if - <tr><td class="showLegend">Flat Folders: </td><td><img src="$sbRoot/images/#if $show.flatten_folders == 1 or $sickbeard.NAMING_FORCE_FOLDERS then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + </div> + + <div id="tags"> + <ul class="tags"> + #if not $show.imdbid + #if $show.genre: + #for $genre in $show.genre[1:-1].split('|') + <a href="<%= anon_url('http://trakt.tv/shows/popular/', genre.lower()) %>" target="_blank" title="View other popular $genre shows on trakt.tv."><li>$genre</li></a> + #end for + #end if + #end if + #if 'year' in $show.imdb_info: + #for $imdbgenre in $show.imdb_info['genres'].replace('Sci-Fi','Science-Fiction').split('|') + <a href="<%= anon_url('http://trakt.tv/shows/popular/', imdbgenre.lower()) %>" target="_blank" title="View other popular $imdbgenre shows on trakt.tv."><li>$imdbgenre</li></a> + #end for + #end if + </ul> + </div> + + <div id="summary"> + <table class="summaryTable pull-left"> + #set $anyQualities, $bestQualities = $Quality.splitQuality(int($show.quality)) + <tr><td class="showLegend">Quality: </td><td> + #if $show.quality in $qualityPresets: + <span class="quality $qualityPresetStrings[$show.quality]">$qualityPresetStrings[$show.quality]</span> + #else: + #if $anyQualities: + <i>Initial:</i> <%=", ".join([Quality.qualityStrings[x] for x in sorted(anyQualities)])%> #if $bestQualities then " </br> " else ""# + #end if + #if $bestQualities: + <i>Replace with:</i> <%=", ".join([Quality.qualityStrings[x] for x in sorted(bestQualities)])%> + #end if + #end if + + #if $show.network and $show.airs: + <tr><td class="showLegend">Originally Airs: </td><td>$show.airs #if not $network_timezones.test_timeformat($show.airs) then " <font color='#FF0000'><b>(invalid Timeformat)</b></font> " else ""# on $show.network</td></tr> + #else if $show.network: + <tr><td class="showLegend">Originally Airs: </td><td>$show.network</td></tr> + #else if $show.airs: + <tr><td class="showLegend">Originally Airs: </td><td>>$show.airs #if not $network_timezones.test_timeformat($show.airs) then " <font color='#FF0000'><b>(invalid Timeformat)</b></font> " else ""#</td></tr> + #end if + <tr><td class="showLegend">Show Status: </td><td>$show.status</td></tr> + <tr><td class="showLegend">Default EP Status: </td><td>$statusStrings[$show.default_ep_status]</td></tr> + #if $showLoc[1]: + <tr><td class="showLegend">Location: </td><td>$showLoc[0]</td></tr> + #else: + <tr><td class="showLegend"><span style="color: red;">Location: </span></td><td><span style="color: red;">$showLoc[0]</span> (dir is missing)</td></tr> + #end if + <tr><td class="showLegend">Scene Name:</td><td>#if $show.exceptions then $exceptions_string else $show.name#</td></tr> + + #if $show.rls_require_words: + <tr><td class="showLegend">Required Words: </td><td>#echo $show.rls_require_words#</td></tr> + #end if + #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> + </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> + </tr> + #end if + + <tr><td class="showLegend">Size:</td><td>$sickbeard.helpers.pretty_filesize(sickbeard.helpers.get_size($showLoc[0]))</td></tr> + + </table> + + <table style="width:180px; float: right; vertical-align: middle; height: 100%;"> + <tr><td class="showLegend">Info Language:</td><td><img src="$sbRoot/images/flags/${show.lang}.png" width="16" height="11" alt="$show.lang" title="$show.lang" /></td></tr> + #if $sickbeard.USE_SUBTITLES + <tr><td class="showLegend">Subtitles: </td><td><img src="$sbRoot/images/#if int($show.subtitles) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + #end if + <tr><td class="showLegend">Flat Folders: </td><td><img src="$sbRoot/images/#if $show.flatten_folders == 1 or $sickbeard.NAMING_FORCE_FOLDERS then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> <tr><td class="showLegend">Paused: </td><td><img src="$sbRoot/images/#if int($show.paused) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - <tr><td class="showLegend">Air-by-Date: </td><td><img src="$sbRoot/images/#if int($show.air_by_date) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - <tr><td class="showLegend">Sports: </td><td><img src="$sbRoot/images/#if int($show.is_sports) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - <tr><td class="showLegend">Anime: </td><td><img src="$sbRoot/images/#if int($show.is_anime) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - <tr><td class="showLegend">DVD Order: </td><td><img src="$sbRoot/images/#if int($show.dvdorder) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - <tr><td class="showLegend">Scene Numbering: </td><td><img src="$sbRoot/images/#if int($show.scene) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - #if $anyQualities + $bestQualities - <tr><td class="showLegend">Archive First Match: </td><td><img src="$sbRoot/images/#if int($show.archive_firstmatch) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> - #end if - </table> - </div> - </div> - </div> - - <div class="clearfix"></div> - - <div class="pull-left" > - Change selected episodes to:</br> - <select id="statusSelect" class="form-control form-control-inline input-sm"> - #for $curStatus in [$WANTED, $SKIPPED, $ARCHIVED, $IGNORED, $FAILED] + sorted($Quality.DOWNLOADED): - #if $curStatus == $DOWNLOADED: - #continue - #end if - <option value="$curStatus">$statusStrings[$curStatus]</option> - #end for - </select> - <input type="hidden" id="showID" value="$show.indexerid" /> - <input type="hidden" id="indexer" value="$show.indexer" /> - <input class="btn btn-inline" type="button" id="changeStatus" value="Go" /> - </div> - - </br> - - <div class="pull-right clearfix" id="checkboxControls"> - <div style="padding-bottom: 5px;"> - <label for="wanted"><span class="wanted"><input type="checkbox" id="wanted" checked="checked" /> Wanted: <b>$epCounts[$Overview.WANTED]</b></span></label> - <label for="qual"><span class="qual"><input type="checkbox" id="qual" checked="checked" /> Low Quality: <b>$epCounts[$Overview.QUAL]</b></span></label> - <label for="good"><span class="good"><input type="checkbox" id="good" checked="checked" /> Downloaded: <b>$epCounts[$Overview.GOOD]</b></span></label> - <label for="skipped"><span class="skipped"><input type="checkbox" id="skipped" checked="checked" /> Skipped: <b>$epCounts[$Overview.SKIPPED]</b></span></label> - <label for="snatched"><span class="snatched"><input type="checkbox" id="snatched" checked="checked" /> Snatched: <b>$epCounts[$Overview.SNATCHED]</b></span></label> - </div> - - <div class="pull-right" > - <button class="btn btn-xs seriesCheck">Select Filtered Episodes</button> - <button class="btn btn-xs clearAll">Clear All</button> - </div> - </div> -<br /> - -<table class="sickbeardTable display_show" cellspacing="0" border="0" cellpadding="0"> + <tr><td class="showLegend">Air-by-Date: </td><td><img src="$sbRoot/images/#if int($show.air_by_date) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + <tr><td class="showLegend">Sports: </td><td><img src="$sbRoot/images/#if int($show.is_sports) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + <tr><td class="showLegend">Anime: </td><td><img src="$sbRoot/images/#if int($show.is_anime) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + <tr><td class="showLegend">DVD Order: </td><td><img src="$sbRoot/images/#if int($show.dvdorder) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + <tr><td class="showLegend">Scene Numbering: </td><td><img src="$sbRoot/images/#if int($show.scene) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + #if $anyQualities + $bestQualities + <tr><td class="showLegend">Archive First Match: </td><td><img src="$sbRoot/images/#if int($show.archive_firstmatch) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + #end if + </table> + </div> + </div> + </div> + + <div class="clearfix"></div> + + <div class="pull-left" > + Change selected episodes to:</br> + <select id="statusSelect" class="form-control form-control-inline input-sm"> + #for $curStatus in [$WANTED, $SKIPPED, $ARCHIVED, $IGNORED, $FAILED] + sorted($Quality.DOWNLOADED): + #if $curStatus == $DOWNLOADED: + #continue + #end if + <option value="$curStatus">$statusStrings[$curStatus]</option> + #end for + </select> + <input type="hidden" id="showID" value="$show.indexerid" /> + <input type="hidden" id="indexer" value="$show.indexer" /> + <input class="btn btn-inline" type="button" id="changeStatus" value="Go" /> + </div> + </br> + + <div class="pull-right clearfix" id="checkboxControls"> + <div style="padding-bottom: 5px;"> + <label for="wanted"><span class="wanted"><input type="checkbox" id="wanted" checked="checked" /> Wanted: <b>$epCounts[$Overview.WANTED]</b></span></label> + <label for="qual"><span class="qual"><input type="checkbox" id="qual" checked="checked" /> Low Quality: <b>$epCounts[$Overview.QUAL]</b></span></label> + <label for="good"><span class="good"><input type="checkbox" id="good" checked="checked" /> Downloaded: <b>$epCounts[$Overview.GOOD]</b></span></label> + <label for="skipped"><span class="skipped"><input type="checkbox" id="skipped" checked="checked" /> Skipped: <b>$epCounts[$Overview.SKIPPED]</b></span></label> + <label for="snatched"><span class="snatched"><input type="checkbox" id="snatched" checked="checked" /> Snatched: <b>$epCounts[$Overview.SNATCHED]</b></span></label> + </div> + + <button id="popover" type="button" class="btn btn-xs">Select Columns</button> + <div class="pull-right" > + <button class="btn btn-xs seriesCheck">Select Filtered Episodes</button> + <button class="btn btn-xs clearAll">Clear All</button> + </div> + </div> +<br /> +<br /> +<br /> + +<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 $odd = 0 + #for $epResult in $sqlResults: + #set $epStr = str($epResult["season"]) + "x" + str($epResult["episode"]) + #if not $epStr in $epCats: + #continue + #end if + #if not $sickbeard.DISPLAY_SHOW_SPECIALS and int($epResult["season"]) == 0: + #continue + #end if + #set $scene = False + #set $scene_anime = False + #if not $show.air_by_date and not $show.is_sports and not $show.is_anime and $show.is_scene: + #set $scene = True + #elif not $show.air_by_date and not $show.is_sports and $show.is_anime and $show.is_scene: + #set $scene_anime = True + #end if + #set ($dfltSeas, $dfltEpis, $dfltAbsolute) = (0, 0, 0) + #if (epResult["season"], epResult["episode"]) in $xem_numbering: + #set ($dfltSeas, $dfltEpis) = $xem_numbering[(epResult["season"], epResult["episode"])] + #end if + #if epResult["absolute_number"] in $xem_absolute_numbering: + #set $dfltAbsolute = $xem_absolute_numbering[epResult["absolute_number"]] + #end if + + #if epResult["absolute_number"] in $scene_absolute_numbering: + #set $scAbsolute = $scene_absolute_numbering[epResult["absolute_number"]] + #set $dfltAbsNumbering = False + #else + #set $scAbsolute = $dfltAbsolute + #set $dfltAbsNumbering = True + #end if - #for $epResult in $sqlResults: - #set $epStr = str($epResult["season"]) + "x" + str($epResult["episode"]) - #if not $epStr in $epCats: - #continue - #end if - - #if not $sickbeard.DISPLAY_SHOW_SPECIALS and int($epResult["season"]) == 0: - #continue - #end if - - #set $scene = False - #set $scene_anime = False - #if not $show.air_by_date and not $show.is_sports and not $show.is_anime and $show.is_scene: - #set $scene = True - #elif not $show.air_by_date and not $show.is_sports and $show.is_anime and $show.is_scene: - #set $scene_anime = True - #end if - - #set ($dfltSeas, $dfltEpis, $dfltAbsolute) = (0, 0, 0) - - #if (epResult["season"], epResult["episode"]) in $xem_numbering: - #set ($dfltSeas, $dfltEpis) = $xem_numbering[(epResult["season"], epResult["episode"])] - #end if - - #if epResult["absolute_number"] in $xem_absolute_numbering: - #set $dfltAbsolute = $xem_absolute_numbering[epResult["absolute_number"]] - #end if - - #if epResult["absolute_number"] in $scene_absolute_numbering: - #set $scAbsolute = $scene_absolute_numbering[epResult["absolute_number"]] - #set $dfltAbsNumbering = False - #else - #set $scAbsolute = $dfltAbsolute - #set $dfltAbsNumbering = True - #end if - - #if (epResult["season"], epResult["episode"]) in $scene_numbering: - #set ($scSeas, $scEpis) = $scene_numbering[(epResult["season"], epResult["episode"])] - #set $dfltEpNumbering = False - #else - #set ($scSeas, $scEpis) = ($dfltSeas, $dfltEpis) - #set $dfltEpNumbering = True - #end if - - #if int($epResult["season"]) != $curSeason: - <tr> - <th class="row-seasonheader" 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> - - <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> - <th class="col-metadata">TBN</th> - <th class="col-ep">Episode</th> - #if $show.is_anime: - <th class="col-ep">Absolute</th> - #end if - #if $scene: - <th class="col-ep">Scene</th> - #end if - #if $scene_anime: - <th class="col-ep">Scene Absolute</th> - #end if - <th class="col-name" - #if ($sickbeard.DISPLAY_FILESIZE == True): - style="min-width: 190px" - #end if - >Name</th> - #if ($sickbeard.DISPLAY_FILESIZE == True): - <th class="col-ep">Size</th> - #end if - <th class="col-airdate">Airdate</th> - #if $sickbeard.DOWNLOAD_URL - <th class="col-ep">Download</th> - #end if - #if $sickbeard.USE_SUBTITLES and $show.subtitles: - <th class="col-subtitles">Subtitles</th> - #end if - <th class="col-status">Status</th> - <th class="col-search">Search</th> - </tr> - #set $curSeason = int($epResult["season"]) - #end if - - #set $epLoc = $epResult["location"] - - <tr class="$Overview.overviewStrings[$epCats[$epStr]] season-$curSeason seasonstyle"> - - <td class="col-checkbox"> - - #if int($epResult["status"]) != $UNAIRED - <input type="checkbox" class="epCheck" id="<%=str(epResult["season"])+'x'+str(epResult["episode"])%>" name="<%=str(epResult["season"]) +"x"+str(epResult["episode"]) %>" /> - #end if - </td> - - <td align="center"><img src="$sbRoot/images/#if $epResult["hasnfo"] == 1 then "nfo.gif\" alt=\"Y" else "nfo-no.gif\" alt=\"N"#" width="23" height="11" /></td> - - <td align="center"><img src="$sbRoot/images/#if $epResult["hastbn"] == 1 then "tbn.gif\" alt=\"Y" else "tbn-no.gif\" alt=\"N"#" width="23" height="11" /></td> - - <td align="center"> - #if $epLoc and $show._location and $epLoc.lower().startswith($show._location.lower()): - #set $epLoc = $epLoc[len($show._location)+1:] - #elif $epLoc and (not $epLoc.lower().startswith($show._location.lower()) or not $show._location): - #set $epLoc = $epLoc - #end if - - #if $epLoc != "" and $epLoc != None: - <span title="$epLoc" class="addQTip">$epResult["episode"]</span> - #else - $epResult["episode"] - #end if - </td> - - #if $show.is_anime: - <td align="center">$epResult["absolute_number"]</td> - #end if - - #if $scene: - <td align="center"> - <input type="text" placeholder="<%=str(dfltSeas) + 'x' + str(dfltEpis)%>" size="6" maxlength="8" - class="sceneSeasonXEpisode form-control input-scene" data-for-season="$epResult["season"]" data-for-episode="$epResult["episode"]" - id="sceneSeasonXEpisode_$show.indexerid<%="_"+str(epResult["season"])+"_"+str(epResult["episode"])%>" - title="Change the value here if scene numbering differs from the indexer episode numbering" - #if $dfltEpNumbering: - value="" - #else - value="<%=str(scSeas) + 'x' + str(scEpis)%>" - #end if - style="padding: 0; text-align: center; max-width: 60px;" /> - </td> - #elif $scene_anime: - <td align="center"> - <input type="text" placeholder="<%=str(dfltAbsolute)%>" size="6" maxlength="8" - class="sceneAbsolute form-control input-scene" data-for-absolute="$epResult["absolute_number"]" - id="sceneAbsolute_$show.indexerid<%="_"+str(epResult["absolute_number"])%>" - title="Change the value here if scene absolute numbering differs from the indexer absolute numbering" - #if $dfltAbsNumbering: - value="" - #else - value="<%=str(scAbsolute)%>" - #end if - style="padding: 0; text-align: center; max-width: 60px;" /> - </td> - #end if - - <td class="col-name"> - #if $epResult["description"] != "" and $epResult["description"] != None: - <img src="$sbRoot/images/info32.png" width="16" height="16" class="plotInfo" alt="" id="plot_info_$show.indexerid<%="_" + str(epResult["season"]) + "_" + str(epResult["episode"])%>" /> - #else: - <img src="$sbRoot/images/info32.png" width="16" height="16" class="plotInfoNone" alt="" /> - #end if - $epResult["name"] - </td> - - #if ($sickbeard.DISPLAY_FILESIZE == True): - <td class="col-ep"> - #if $epResult["file_size"]: - #set $file_size = $sickbeard.helpers.pretty_filesize($epResult["file_size"]) - $file_size - #end if - </td> - #end if - <td class="col-airdate"> - <span class="${fuzzydate}">#if int($epResult['airdate']) == 1 then 'never' else $sbdatetime.sbdatetime.sbfdate($sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($epResult['airdate'],$show.airs,$show.network)))#</span> - </td> - - #if $sickbeard.DOWNLOAD_URL and $epResult['location'] - <td> - #set $filename = $epResult['location'] - #for $rootDir in $sickbeard.ROOT_DIRS.split('|') - #if $rootDir.startswith('/') - #set $filename = $filename.replace($rootDir, "") - #end if - #end for - #set $filename = $sickbeard.DOWNLOAD_URL + $urllib.quote($filename.encode('utf8')) - <center><a href="$filename">Download</a></center> - </td> - #elif $sickbeard.DOWNLOAD_URL - <td></td> - #end if - - #if $sickbeard.USE_SUBTITLES and $show.subtitles: - <td class="col-subtitles" align="center"> - #if $epResult["subtitles"]: - #for $sub_lang in subliminal.language.language_list([x.strip() for x in $epResult["subtitles"].split(',') if x != ""]): - #if sub_lang.alpha2 != "" - <img src="$sbRoot/images/flags/${sub_lang.alpha2}.png" width="16" height="11" alt="${sub_lang}" /> + #if (epResult["season"], epResult["episode"]) in $scene_numbering: + #set ($scSeas, $scEpis) = $scene_numbering[(epResult["season"], epResult["episode"])] + #set $dfltEpNumbering = False + #else + #set ($scSeas, $scEpis) = ($dfltSeas, $dfltEpis) + #set $dfltEpNumbering = True + #end if + + #set $epLoc = $epResult["location"] + + #if int($epResult["season"]) != $curSeason: + #if $curSeason == -1: + <thead> + <tr class="seasoncols" style="display:none;"> + <th data-sorter="false" data-priority="critical" class="col-checkbox"><input type="checkbox" class="seasonCheck"/></th> + <th data-sorter="false" class="col-metadata">NFO</th> + <th data-sorter="false" class="col-metadata">TBN</th> + <th data-sorter="false" class="col-ep">Episode</th> + <th data-sorter="false" #if not $show.is_anime then "class=\"col-ep columnSelector-false\"" else "class=\"col-ep\""#>Absolute</th> + <th data-sorter="false" #if not $scene then "class=\"col-ep columnSelector-false\"" else "class=\"col-ep\""#>Scene</th> + <th data-sorter="false" #if not $scene_anime then "class=\"col-ep columnSelector-false\"" else "class=\"col-ep\""#>Scene Absolute</th> + <th data-sorter="false" class="col-name">Name</th> + <th data-sorter="false" class="col-name columnSelector-false">File Name</th> + <th data-sorter="false" class="col-ep columnSelector-false">Size</th> + <th data-sorter="false" class="col-airdate">Airdate</th> + <th data-sorter="false" #if not $sickbeard.DOWNLOAD_URL then "class=\"col-ep columnSelector-false\"" else "class=\"col-ep\""#>Download</th> + <th data-sorter="false" #if not $sickbeard.USE_SUBTITLES then "class=\"col-ep columnSelector-false\"" else "class=\"col-ep\""#>Subtitles</th> + <th data-sorter="false" class="col-status">Status</th> + <th data-sorter="false" class="col-search">Search</th> + </tr> + </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> + <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> + <th class="col-metadata">TBN</th> + <th class="col-ep">Episode</th> + <th class="col-ep">Absolute</th> + <th class="col-ep">Scene</th> + <th class="col-ep">Scene Absolute</th> + <th class="col-name">Name</th> + <th class="col-name">File Name</th> + <th class="col-ep">Size</th> + <th class="col-airdate">Airdate</th> + <th class="col-ep">Download</th> + <th class="col-ep">Subtitles</th> + <th class="col-status">Status</th> + <th class="col-search">Search</th> + </tr> + #else: + </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> + <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> + <th class="col-metadata">TBN</th> + <th class="col-ep">Episode</th> + <th class="col-ep">Absolute</th> + <th class="col-ep">Scene</th> + <th class="col-ep">Scene Absolute</th> + <th class="col-name">Name</th> + <th class="col-name">File Name</th> + <th class="col-ep">Size</th> + <th class="col-airdate">Airdate</th> + <th class="col-ep">Download</th> + <th class="col-ep">Subtitles</th> + <th class="col-status">Status</th> + <th class="col-search">Search</th> + </tr> + #end if + </tbody> + <tbody> + #set $curSeason = int($epResult["season"]) + #end if + #set $epLoc = $epResult["location"] + <tr class="$Overview.overviewStrings[$epCats[$epStr]] season-$curSeason seasonstyle"> + <td class="col-checkbox"> + #if int($epResult["status"]) != $UNAIRED + <input type="checkbox" class="epCheck" id="<%=str(epResult["season"])+'x'+str(epResult["episode"])%>" name="<%=str(epResult["season"]) +"x"+str(epResult["episode"]) %>" /> + #end if + </td> + <td align="center"><img src="$sbRoot/images/#if $epResult["hasnfo"] == 1 then "nfo.gif\" alt=\"Y" else "nfo-no.gif\" alt=\"N"#" width="23" height="11" /></td> + <td align="center"><img src="$sbRoot/images/#if $epResult["hastbn"] == 1 then "tbn.gif\" alt=\"Y" else "tbn-no.gif\" alt=\"N"#" width="23" height="11" /></td> + <td align="center"> + #if $epLoc and $show._location and $epLoc.lower().startswith($show._location.lower()): + #set $epLoc = $epLoc[len($show._location)+1:] + #elif $epLoc and (not $epLoc.lower().startswith($show._location.lower()) or not $show._location): + #set $epLoc = $epLoc + #end if + + #if $epLoc != "" and $epLoc != None: + <span title="$epLoc" class="addQTip">$epResult["episode"]</span> #else - <img src="$sbRoot/images/flags/unknown.png" width="16" height="11" alt="Unknown" /> + $epResult["episode"] #end if - #end for - #end if - </td> - #end if - - #set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($epResult["status"])) - #if $curQuality != Quality.NONE: - <td class="col-status">$statusStrings[$curStatus] <span class="quality $Quality.qualityStrings[$curQuality].replace("720p","HD720p").replace("1080p","HD1080p").replace("RawHD TV", "RawHD").replace("HD TV", "HD720p")">$Quality.qualityStrings[$curQuality]</span></td> - #else: - <td class="col-status">$statusStrings[$curStatus]</td> - #end if - - <td class="col-search"> - #if int($epResult["season"]) != 0: - #if ( int($epResult["status"]) in $Quality.SNATCHED or int($epResult["status"]) in $Quality.DOWNLOADED ) and $sickbeard.USE_FAILED_DOWNLOADS: - <a class="epRetry" id="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" name="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" href="retryEpisode?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/search16.png" height="16" alt="retry" title="Retry Download" /></a> - #else: - <a class="epSearch" id="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" name="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" href="searchEpisode?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/search16.png" width="16" height="16" alt="search" title="Manual Search" /></a> - #end if - #end if - - #if $sickbeard.USE_SUBTITLES and $show.subtitles and len(set(str($epResult["subtitles"]).split(',')).intersection(set($subtitles.wantedLanguages()))) < len($subtitles.wantedLanguages()) and $epResult["location"] - <a class="epSubtitlesSearch" href="searchEpisodeSubtitles?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/closed_captioning.png" height="16" alt="search subtitles" title="Search Subtitles" /></a> - #end if - </td> - </tr> - - #end for - + </td> + <td align="center">$epResult["absolute_number"]</td> + <td align="center"> + <input type="text" placeholder="<%=str(dfltSeas) + 'x' + str(dfltEpis)%>" size="6" maxlength="8" + class="sceneSeasonXEpisode form-control input-scene" data-for-season="$epResult["season"]" data-for-episode="$epResult["episode"]" + id="sceneSeasonXEpisode_$show.indexerid<%="_"+str(epResult["season"])+"_"+str(epResult["episode"])%>" + title="Change the value here if scene numbering differs from the indexer episode numbering" + #if $dfltEpNumbering: + value="" + #else + value="<%=str(scSeas) + 'x' + str(scEpis)%>" + #end if + style="padding: 0; text-align: center; max-width: 60px;" /> + </td> + <td align="center"> + <input type="text" placeholder="<%=str(dfltAbsolute)%>" size="6" maxlength="8" + class="sceneAbsolute form-control input-scene" data-for-absolute="$epResult["absolute_number"]" + id="sceneAbsolute_$show.indexerid<%="_"+str(epResult["absolute_number"])%>" + title="Change the value here if scene absolute numbering differs from the indexer absolute numbering" + #if $dfltAbsNumbering: + value="" + #else + value="<%=str(scAbsolute)%>" + #end if + style="padding: 0; text-align: center; max-width: 60px;" /> + </td> + <td class="col-name"> + #if $epResult["description"] != "" and $epResult["description"] != None: + <img src="$sbRoot/images/info32.png" width="16" height="16" class="plotInfo" alt="" id="plot_info_$show.indexerid<%="_" + str(epResult["season"]) + "_" + str(epResult["episode"])%>" /> + #else: + <img src="$sbRoot/images/info32.png" width="16" height="16" class="plotInfoNone" alt="" /> + #end if + $epResult["name"] + </td> + <td class="col-name]"> + #if $epResult['location'] + #set $filename = $epResult['location'] + #for $rootDir in $sickbeard.ROOT_DIRS.split('|') + #if $rootDir.startswith('/') + #set $filename = ntpath.basename($filename) + #end if + #end for + $filename + #end if + </td> + <td class="col-ep"> + #if $epResult["file_size"]: + #set $file_size = $sickbeard.helpers.pretty_filesize($epResult["file_size"]) + $file_size + #end if + </td> + <td class="col-airdate"> + <span class="${fuzzydate}">#if int($epResult['airdate']) == 1 then 'never' else $sbdatetime.sbdatetime.sbfdate($sbdatetime.sbdatetime.convert_to_setting($network_timezones.parse_date_time($epResult['airdate'],$show.airs,$show.network)))#</span> + </td> + <td> + #if $sickbeard.DOWNLOAD_URL and $epResult['location'] + #if $epResult['location'] + #set $filename = $epResult['location'] + #for $rootDir in $sickbeard.ROOT_DIRS.split('|') + #if $rootDir.startswith('/') + #set $filename = $filename.replace($rootDir, "") + #end if + #end for + #set $filename = $sickbeard.DOWNLOAD_URL + $urllib.quote($filename.encode('utf8')) + <center><a href="$filename">Download</a></center> + #end if + #end if + </td> + <td class="col-subtitles" align="center"> + #if $epResult["subtitles"]: + #for $sub_lang in subliminal.language.language_list([x.strip() for x in $epResult["subtitles"].split(',') if x != ""]): + #if sub_lang.alpha2 != "" + <img src="$sbRoot/images/flags/${sub_lang.alpha2}.png" width="16" height="11" alt="${sub_lang}" /> + #else + <img src="$sbRoot/images/flags/unknown.png" width="16" height="11" alt="Unknown" /> + #end if + #end for + #end if + </td> + #set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($epResult["status"])) + #if $curQuality != Quality.NONE: + <td class="col-status">$statusStrings[$curStatus] <span class="quality $Quality.qualityStrings[$curQuality].replace("720p","HD720p").replace("1080p","HD1080p").replace("RawHD TV", "RawHD").replace("HD TV", "HD720p")">$Quality.qualityStrings[$curQuality]</span></td> + #else: + <td class="col-status">$statusStrings[$curStatus]</td> + #end if + <td class="col-search"> + #if int($epResult["season"]) != 0: + #if ( int($epResult["status"]) in $Quality.SNATCHED or int($epResult["status"]) in $Quality.DOWNLOADED ) and $sickbeard.USE_FAILED_DOWNLOADS: + <a class="epRetry" id="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" name="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" href="retryEpisode?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/search16.png" height="16" alt="retry" title="Retry Download" /></a> + #else: + <a class="epSearch" id="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" name="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" href="searchEpisode?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/search16.png" width="16" height="16" alt="search" title="Manual Search" /></a> + #end if + #end if + #if $sickbeard.USE_SUBTITLES and $show.subtitles and len(set(str($epResult["subtitles"]).split(',')).intersection(set($subtitles.wantedLanguages()))) < len($subtitles.wantedLanguages()) and $epResult["location"] + <a class="epSubtitlesSearch" href="searchEpisodeSubtitles?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/closed_captioning.png" height="16" alt="search subtitles" title="Search Subtitles" /></a> + #end if + </td> + </tr> + #end for + </tbody> </table> - + <!--Begin - Bootstrap Modal--> <div id="manualSearchModalFailed" class="modal fade"> diff --git a/gui/slick/interfaces/default/editShow.tmpl b/gui/slick/interfaces/default/editShow.tmpl index aafd072732e54d27d7e9909c90c3b68af3dd2403..ff40985259a683ceebc192deca44800e4d3e1b2d 100644 --- a/gui/slick/interfaces/default/editShow.tmpl +++ b/gui/slick/interfaces/default/editShow.tmpl @@ -78,7 +78,7 @@ <b>Info Language:</b><br /> (this will only affect the language of the retrieved metadata file contents and episode filenames)<br /> -<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm bfh-languages" data-language="en" data-available="#echo ','.join($sickbeard.indexerApi().config['valid_languages'])#"></select><br /> +<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm bfh-languages" data-language="#echo $sickbeard.INDEXER_DEFAULT_LANGUAGE#" data-available="#echo ','.join($sickbeard.indexerApi().config['valid_languages'])#"></select><br /> <br /> <b>Flatten files (no folders): </b> <input type="checkbox" name="flatten_folders" #if $show.flatten_folders == 1 and not $sickbeard.NAMING_FORCE_FOLDERS then "checked=\"checked\"" else ""# #if $sickbeard.NAMING_FORCE_FOLDERS then "disabled=\"disabled\"" else ""#/><br /> diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl index 2bc4e16b18687a751426fdf66885b47f573b24da..f46ad2d63d7d2eb66fd1eacb81262034c3cb4cef 100644 --- a/gui/slick/interfaces/default/inc_top.tmpl +++ b/gui/slick/interfaces/default/inc_top.tmpl @@ -119,6 +119,9 @@ \$("#SubMenu a:contains('Notification')").addClass('btn').html('<span class="ui-icon ui-icon-note pull-left"></span> Notifications'); \$("#SubMenu a:contains('Update show in KODI')").addClass('btn').html('<span class="submenu-icon-kodi pull-left"></span> Update show in KODI'); \$("#SubMenu a[href$='/home/updateKODI/']").addClass('btn').html('<span class="submenu-icon-kodi pull-left"></span> Update KODI'); + \$("#SubMenu a:contains('Pause')").addClass('btn').html('<span class="ui-icon ui-icon-pause pull-left"></span> Pause'); + \$("#SubMenu a:contains('Resume')").addClass('btn').html('<span class="ui-icon ui-icon-play pull-left"></span> Resume'); + } \$(document).ready(function() { diff --git a/gui/slick/js/ajaxNotifications.js b/gui/slick/js/ajaxNotifications.js index 92b2fc22b853b4d2c880e66d7743424e30d410af..f1b38d7cdfab4198322c67ad140574a0332e5ab2 100644 --- a/gui/slick/js/ajaxNotifications.js +++ b/gui/slick/js/ajaxNotifications.js @@ -1,31 +1,42 @@ -var message_url = sbRoot + '/ui/get_messages'; +var message_url = sbRoot + '/ui/get_messages', + test = !1; +PNotify.prototype.options.addclass = 'stack-bottomright'; +PNotify.prototype.options.buttons.closer_hover = !1; +PNotify.prototype.options.delay = 5000; +PNotify.prototype.options.desktop = {desktop: !0, icon: 'data:image/png;base64,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'}; +PNotify.prototype.options.hide = !0; +PNotify.prototype.options.history = !1; +PNotify.prototype.options.shadow = !1; +PNotify.prototype.options.stack = {dir1: 'up', dir2: 'left', firstpos1: 25, firstpos2: 25}; PNotify.prototype.options.styling = 'jqueryui'; -PNotify.prototype.options.buttons.closer_hover = false; -PNotify.prototype.options.delay = 4000; PNotify.prototype.options.width = '340px'; -PNotify.prototype.options.shadow = false; -PNotify.prototype.options.addclass = 'stack-bottomright'; -PNotify.prototype.options.stack = {'dir1': 'up', 'dir2': 'left', 'firstpos1': 25, 'firstpos2': 25}; +PNotify.desktop.permission(); + +function displayPNotify(type, title, message) { + var notification = new PNotify({ + type: type, title: title, + text: message.replace(/<br[\s\/]*(?:\s[^>]*)?>/ig, "\n") + .replace(/<[\/]?b(?:\s[^>]*)?>/ig, '*') + .replace(/<i(?:\s[^>]*)?>/ig, '[').replace(/<[\/]i>/ig, ']') + .replace(/<(?:[\/]?ul|\/li)(?:\s[^>]*)?>/ig, '').replace(/<li(?:\s[^>]*)?>/ig, "\n" + '* ') + }); +} function check_notifications() { - if(document.visibilityState == 'visible') { - $.getJSON(message_url, function(data){ - $.each(data, function(name,data){ - new PNotify({ - type: data.type, - hide: data.type == 'notice', - title: data.title, - text: data.message, - history: false - }); - }); - }); - } - - setTimeout(check_notifications, 3000) + if ('visible' == document.visibilityState) { + $.getJSON(message_url, function (data) { + $.each(data, function (name, data) { + displayPNotify(data.type, data.title, data.message) + }); + }); + } + setTimeout(check_notifications, 3000) } $(document).ready(function(){ - check_notifications(); + check_notifications(); + if (test) { + displayPNotify('notice', 'test', 'test<br/><i class="test-class">hello <b>world</b></i><ul><li>item 1</li><li>item 2</li></ul>'); + } }); \ No newline at end of file diff --git a/gui/slick/js/configNotifications.js b/gui/slick/js/configNotifications.js index 2d88287e9bd77a970b7a28648c6e81ef214ac399..f0b52ea97cb035e6755c8f8a574122d57dc43961 100644 --- a/gui/slick/js/configNotifications.js +++ b/gui/slick/js/configNotifications.js @@ -56,24 +56,44 @@ $(document).ready(function(){ }); }); - $('#testPLEX').click(function () { - var plex_host = $.trim($('#plex_host').val()); - var plex_username = $.trim($('#plex_username').val()); - var plex_password = $.trim($('#plex_password').val()); - if (!plex_host) { - $('#testPLEX-result').html('Please fill out the necessary fields above.'); + $('#testPMC').click(function () { + var plex_host = $.trim($('#plex_host').val()); + var plex_username = $.trim($('#plex_username').val()); + var plex_password = $.trim($('#plex_password').val()); + if (!plex_host) { + $('#testPMC-result').html('Please fill out the necessary fields above.'); $('#plex_host').addClass('warning'); - return; - } - $('#plex_host').removeClass('warning'); + return; + } + $('#plex_host').removeClass('warning'); $(this).prop('disabled', true); - $('#testPLEX-result').html(loading); - $.get(sbRoot + '/home/testPLEX', {'host': plex_host, 'username': plex_username, 'password': plex_password}) - .done(function (data) { - $('#testPLEX-result').html(data); - $('#testPLEX').prop('disabled', false); - }); - }); + $('#testPMC-result').html(loading); + $.get(sbRoot + '/home/testPMC', {'host': plex_host, 'username': plex_username, 'password': plex_password}) + .done(function (data) { + $('#testPMC-result').html(data); + $('#testPMC').prop('disabled', false); + }); + }); + + $('#testPMS').click(function () { + var plex_server_host = $.trim($('#plex_server_host').val()); + var plex_username = $.trim($('#plex_username').val()); + var plex_password = $.trim($('#plex_password').val()); + var plex_server_token = $.trim($('#plex_server_token').val()); + if (!plex_server_host) { + $('#testPMS-result').html('Please fill out the necessary fields above.'); + $('#plex_server_host').addClass('warning'); + return; + } + $('#plex_server_host').removeClass('warning'); + $(this).prop('disabled', true); + $('#testPMS-result').html(loading); + $.get(sbRoot + '/home/testPMS', {'host': plex_server_host, 'username': plex_username, 'password': plex_password, 'plex_server_token': plex_server_token}) + .done(function (data) { + $('#testPMS-result').html(data); + $('#testPMS').prop('disabled', false); + }); + }); $('#testBoxcar').click(function() { var boxcar_username = $.trim($('#boxcar_username').val()); diff --git a/gui/slick/js/configSearch.js b/gui/slick/js/configSearch.js index f586a488c832b21043c42adbc7921a3f95083776..1840bd68d60d86c72343173fdca9a51afde7f8bb 100644 --- a/gui/slick/js/configSearch.js +++ b/gui/slick/js/configSearch.js @@ -150,6 +150,12 @@ $(document).ready(function(){ $(torrent_verify_rtorrent).show(); $(torrent_auth_type_option).show(); //$('#directory_title').text(client + directory); + } else if ('qbittorrent' == selectedProvider){ + client = 'qbittorrent'; + $(torrent_path_option).hide(); + $(torrent_label_option).hide(); + $(torrent_label_anime_option).hide(); + $('#host_desc_torrent').text('URL to your qbittorrent client (e.g. http://localhost:8080)'); } $('#host_title').text(client + host); $('#username_title').text(client + username); diff --git a/gui/slick/js/displayShow.js b/gui/slick/js/displayShow.js index 4825c2cbcc45420f9fd67a2aa8199a504fd0c04f..fb13af78ae2b75e31d81470b27313683f018dd9f 100644 --- a/gui/slick/js/displayShow.js +++ b/gui/slick/js/displayShow.js @@ -259,5 +259,5 @@ $(document).ready(function () { sceneAbsolute = m[1]; } setAbsoluteSceneNumbering(forAbsolute, sceneAbsolute); - }); + }); }); diff --git a/gui/slick/js/lib/pnotify.custom.min.js b/gui/slick/js/lib/pnotify.custom.min.js index 704c85ae41ad58fb3d4bd2142a226ec6683d8b94..f927d92712aa970d3baeabde56c84448bb888a33 100644 Binary files a/gui/slick/js/lib/pnotify.custom.min.js and b/gui/slick/js/lib/pnotify.custom.min.js differ diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 2a9b9e0900c2db4036ad9e7cc7d570173352e8a3..f20e8ab68c229664d0557e75c5ddc01f0e539ef9 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -38,7 +38,7 @@ from sickbeard import providers, metadata, config, webserveInit from sickbeard.providers.generic import GenericProvider from providers import ezrss, btn, newznab, womble, thepiratebay, oldpiratebay, torrentleech, kat, iptorrents, \ omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, hounddawgs, nextgen, speedcd, nyaatorrents, animenzb, torrentbytes, animezb, \ - freshontv, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, eztv + freshontv, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, eztv, scenetime from sickbeard.config import CheckSection, check_setting_int, check_setting_str, check_setting_float, ConfigMigrator, \ naming_ep_type from sickbeard import searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser, \ @@ -482,7 +482,6 @@ COMING_EPS_LAYOUT = None COMING_EPS_DISPLAY_PAUSED = False COMING_EPS_SORT = None COMING_EPS_MISSED_RANGE = None -DISPLAY_FILESIZE = False FUZZY_DATING = False TRIM_ZERO = False DATE_PRESET = None @@ -802,7 +801,7 @@ def initialize(consoleLogging=True): NZB_METHOD = 'blackhole' TORRENT_METHOD = check_setting_str(CFG, 'General', 'torrent_method', 'blackhole') - if TORRENT_METHOD not in ('blackhole', 'utorrent', 'transmission', 'deluge', 'download_station', 'rtorrent'): + if TORRENT_METHOD not in ('blackhole', 'utorrent', 'transmission', 'deluge', 'download_station', 'rtorrent', 'qbittorrent'): TORRENT_METHOD = 'blackhole' DOWNLOAD_PROPERS = bool(check_setting_int(CFG, 'General', 'download_propers', 1)) @@ -1123,7 +1122,6 @@ def initialize(consoleLogging=True): COMING_EPS_DISPLAY_PAUSED = bool(check_setting_int(CFG, 'GUI', 'coming_eps_display_paused', 0)) COMING_EPS_SORT = check_setting_str(CFG, 'GUI', 'coming_eps_sort', 'date') COMING_EPS_MISSED_RANGE = check_setting_int(CFG, 'GUI', 'coming_eps_missed_range', 7) - DISPLAY_FILESIZE = bool(check_setting_int(CFG, 'GUI', 'display_filesize', 0)) FUZZY_DATING = bool(check_setting_int(CFG, 'GUI', 'fuzzy_dating', 0)) TRIM_ZERO = bool(check_setting_int(CFG, 'GUI', 'trim_zero', 0)) DATE_PRESET = check_setting_str(CFG, 'GUI', 'date_preset', '%x') @@ -2040,7 +2038,6 @@ def save_config(): new_config['GUI']['coming_eps_display_paused'] = int(COMING_EPS_DISPLAY_PAUSED) new_config['GUI']['coming_eps_sort'] = COMING_EPS_SORT new_config['GUI']['coming_eps_missed_range'] = int(COMING_EPS_MISSED_RANGE) - new_config['GUI']['display_filesize'] = int(DISPLAY_FILESIZE) new_config['GUI']['fuzzy_dating'] = int(FUZZY_DATING) new_config['GUI']['trim_zero'] = int(TRIM_ZERO) new_config['GUI']['date_preset'] = DATE_PRESET diff --git a/sickbeard/classes.py b/sickbeard/classes.py index 92bbc23e11cb067beee83b1757eaf503fa10c8f1..ec8fce996f5191dcae3f9ae1936893d66a37dcf8 100644 --- a/sickbeard/classes.py +++ b/sickbeard/classes.py @@ -276,6 +276,6 @@ class UIError(): """ def __init__(self, message): - self.title = sys.exc_info()[-2] + self.title = sys.exc_info()[-2] or message self.message = message self.time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') \ No newline at end of file diff --git a/sickbeard/clients/__init__.py b/sickbeard/clients/__init__.py index 84646ac32955d2beb27b92ecf9ab13d609be43fc..432efc797127ae729371bfd42f03f4b63d00f4f8 100644 --- a/sickbeard/clients/__init__.py +++ b/sickbeard/clients/__init__.py @@ -20,7 +20,8 @@ __all__ = ['utorrent', 'transmission', 'deluge', 'download_station', - 'rtorrent' + 'rtorrent', + 'qbittorrent' ] import sickbeard @@ -29,13 +30,28 @@ from os import sys # Mapping error status codes to official W3C names http_error_code = { + 100: 'Continue', + 101: 'Switching Protocols', + 102: 'Processing', + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 207: 'Multi-Status', + 208: 'Already Reported', + 226: 'IM Used', 300: 'Multiple Choices', 301: 'Moved Permanently', 302: 'Found', 303: 'See Other', 304: 'Not Modified', 305: 'Use Proxy', + 306: 'Switch Proxy', 307: 'Temporary Redirect', + 308: 'Permanent Redirect', 400: 'Bad Request', 401: 'Unauthorized', 402: 'Payment Required', @@ -54,13 +70,45 @@ http_error_code = { 415: 'Unsupported Media Type', 416: 'Requested Range Not Satisfiable', 417: 'Expectation Failed', + 418: 'Im a teapot', + 419: 'Authentication Timeout', + 420: 'Enhance Your Calm', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 440: 'Login Timeout', + 444: 'No Response', + 449: 'Retry With', + 450: 'Blocked by Windows Parental Controls', + 451: 'Redirect', + 451: 'Unavailable For Legal Reasons', + 494: 'Request Header Too Large', + 495: 'Cert Error', + 496: 'No Cert', + 497: 'HTTP to HTTPS', + 498: 'Token expired/invalid', + 499: 'Client Closed Request', + 499: 'Token required', 500: 'Internal Server Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout', 505: 'HTTP Version Not Supported', - 524: 'Request to host timedout waiting for reply back' + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 509: 'Bandwidth Limit Exceeded', + 510: 'Not Extended', + 511: 'Network Authentication Required', + 522: 'Cloudfare Connection timed out', + 524: 'Request to host timedout waiting for reply back', + 598: 'Network read timeout error', + 599: 'Network connect timeout error ' } default_host = {'utorrent': 'http://localhost:8000', @@ -68,6 +116,7 @@ default_host = {'utorrent': 'http://localhost:8000', 'deluge': 'http://localhost:8112', 'download_station': 'http://localhost:5000', 'rtorrent': 'scgi://localhost:5000', + 'qbittorrent': 'http://localhost:8080' } @@ -82,4 +131,4 @@ def getClientIstance(name): module = getClientModule(name) className = module.api.__class__.__name__ - return getattr(module, className) \ No newline at end of file + return getattr(module, className) diff --git a/sickbeard/clients/generic.py b/sickbeard/clients/generic.py index 7940ab17da62cc70cf64a1271facf9acc9b40d5b..3dfc58dde954a368363ced1c354d0eae3e2d755d 100644 --- a/sickbeard/clients/generic.py +++ b/sickbeard/clients/generic.py @@ -61,7 +61,7 @@ class GenericClient(object): logger.log(self.name + u': Invalid HTTP Request ' + str(e), logger.ERROR) return False except requests.exceptions.Timeout, e: - logger.log(self.name + u': Connection Timeout ' + str(e), logger.ERROR) + logger.log(self.name + u': Connection Timeout ' + str(e), logger.WARNING) return False except Exception, e: logger.log(self.name + u': Unknown exception raised when send torrent to ' + self.name + ': ' + str(e), diff --git a/sickbeard/clients/qbittorrent.py b/sickbeard/clients/qbittorrent.py new file mode 100644 index 0000000000000000000000000000000000000000..5923c274cc75b298107b5a839f074d978a0447aa --- /dev/null +++ b/sickbeard/clients/qbittorrent.py @@ -0,0 +1,73 @@ +# Author: Mr_Orange <mr_orange@hotmail.it> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import sickbeard +from sickbeard import logger +from sickbeard.clients.generic import GenericClient +from lib import requests +from lib.requests.auth import HTTPDigestAuth + +class qbittorrentAPI(GenericClient): + def __init__(self, host=None, username=None, password=None): + + super(qbittorrentAPI, self).__init__('qbittorrent', host, username, password) + + self.url = self.host + self.session.auth = HTTPDigestAuth(self.username, self.password); + + def _get_auth(self): + + try: + self.response = self.session.get(self.host, verify=False) + self.auth = self.response.content + except: + return None + + return self.auth if not self.response.status_code == 404 else None + + def _add_torrent_uri(self, result): + + self.url = self.host+'command/download' + data = {'urls': result.url} + return self._request(method='post', data=data) + + def _add_torrent_file(self, result): + + self.url = self.host+'command/upload' + files = {'torrents': (result.name + '.torrent', result.content)} + return self._request(method='post', files=files) + + def _set_torrent_priority(self, result): + + self.url = self.host+'command/decreasePrio ' + if result.priority == 1: + self.url = self.host+'command/increasePrio' + + data = {'hashes': result.hash} + return self._request(method='post', data=data) + + def _set_torrent_pause(self, result): + + self.url = self.host+'command/resume' + if sickbeard.TORRENT_PAUSED: + self.url = self.host+'command/pause' + + data = {'hash': result.hash} + return self._request(method='post', data=data) + +api = qbittorrentAPI() \ No newline at end of file diff --git a/sickbeard/config.py b/sickbeard/config.py index 94413e32fcfe7ced4e738f164806859fd4961723..9a25153026e0aa6043c53cf136c9640ac9d9d325 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -230,7 +230,7 @@ def change_DOWNLOAD_PROPERS(download_propers): else: sickbeard.properFinderScheduler.enable = False sickbeard.traktCheckerScheduler.silent = True - logger.log(u"Waiting for the PROPERFINDER thread to exit", logger.INFO) + logger.log(u"Stopping PROPERFINDER thread", logger.INFO) def change_USE_TRAKT(use_trakt): use_trakt = checkbox_to_value(use_trakt) diff --git a/sickbeard/encodingKludge.py b/sickbeard/encodingKludge.py index ffef1d2b348d73e7b5f1ad007567646bce6a9838..fadb28d0ff178d05a0f080d2936eeab2f9fc4a7c 100644 --- a/sickbeard/encodingKludge.py +++ b/sickbeard/encodingKludge.py @@ -20,6 +20,21 @@ import os import chardet import sickbeard +def fixStupidEncodings(x, silent=False): + if type(x) == str: + try: + return x.decode(sickbeard.SYS_ENCODING) + except UnicodeDecodeError: + logger.log(u"Unable to decode value: " + repr(x), logger.ERROR) + return None + elif type(x) == unicode: + return x + else: + logger.log( + u"Unknown value passed in, ignoring it: " + str(type(x)) + " (" + repr(x) + ":" + repr(type(x)) + ")", + logger.DEBUG if silent else logger.ERROR) + return None + def _toUnicode(x): try: if not isinstance(x, unicode): diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index e3451266735199e79a47db046c9c5364637c2908..1d0d041d9e4fe6701834ba2b11dcc5b52de07fae 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -1037,6 +1037,9 @@ def validateShow(show, season=None, episode=None): if indexer_lang and not indexer_lang == sickbeard.INDEXER_DEFAULT_LANGUAGE: lINDEXER_API_PARMS['language'] = indexer_lang + if show.dvdorder != 0: + lINDEXER_API_PARMS['dvdorder'] = True + t = sickbeard.indexerApi(show.indexer).indexer(**lINDEXER_API_PARMS) if season is None and episode is None: return t @@ -1247,6 +1250,16 @@ def _getTempDir(): return os.path.join(tempfile.gettempdir(), "sickrage-%s" % (uid)) +def codeDescription(status_code): + """ + Returns the description of the URL error code + """ + if status_code in clients.http_error_code: + return clients.http_error_code[status_code] + else: + logger.log(u"Unknown error code. Please submit an issue", logger.WARNING) + return 'unknown' + def getURL(url, post_data=None, params=None, headers={}, timeout=30, session=None, json=False, proxyGlypeProxySSLwarning=None): """ Returns a byte-string retrieved from the url provider. @@ -1283,7 +1296,7 @@ def getURL(url, post_data=None, params=None, headers={}, timeout=30, session=Non if not resp.ok: logger.log(u"Requested url " + url + " returned status code is " + str( - resp.status_code) + ': ' + clients.http_error_code[resp.status_code], logger.DEBUG) + resp.status_code) + ': ' + codeDescription(resp.status_code), logger.DEBUG) return if proxyGlypeProxySSLwarning is not None: @@ -1292,7 +1305,7 @@ def getURL(url, post_data=None, params=None, headers={}, timeout=30, session=Non if not resp.ok: logger.log(u"GlypeProxySSLwarning: Requested url " + url + " returned status code is " + str( - resp.status_code) + ': ' + clients.http_error_code[resp.status_code], logger.DEBUG) + resp.status_code) + ': ' + codeDescription(resp.status_code), logger.DEBUG) return except requests.exceptions.HTTPError, e: @@ -1334,9 +1347,10 @@ def download_file(url, filename, session=None): try: resp = session.get(url) + if not resp.ok: logger.log(u"Requested url " + url + " returned status code is " + str( - resp.status_code) + ': ' + clients.http_error_code[resp.status_code], logger.DEBUG) + resp.status_code) + ': ' + codeDescription(resp.status_code), logger.DEBUG) return False with open(filename, 'wb') as fp: @@ -1409,7 +1423,11 @@ def get_size(start_path='.'): for dirpath, dirnames, filenames in ek.ek(os.walk, start_path): for f in filenames: fp = ek.ek(os.path.join, dirpath, f) - total_size += ek.ek(os.path.getsize, fp) + try: + total_size += ek.ek(os.path.getsize, fp) + except OSError as e: + logger.log('Unable to get size for file {filePath}. Error msg is: {errorMsg}'.format(filePath=fp, errorMsg=str(e)), logger.ERROR) + logger.log(traceback.format_exc(), logger.DEBUG) return total_size def generateApiKey(): @@ -1537,4 +1555,4 @@ def pretty_time_delta(seconds): elif minutes > 0: return '%s%02dm%02ds' % (sign_string, minutes, seconds) else: - return '%s%02ds' % (sign_string, seconds) \ No newline at end of file + return '%s%02ds' % (sign_string, seconds) diff --git a/sickbeard/metadata/generic.py b/sickbeard/metadata/generic.py index bd14875d003131092c3a2e624490ddc5851aa351..098b770424f5aacceb5e96a38d26e3942fee850e 100644 --- a/sickbeard/metadata/generic.py +++ b/sickbeard/metadata/generic.py @@ -951,11 +951,6 @@ class GenericMetadata(): name = showXML.findtext('title') - try: - indexer = int(showXML.findtext('indexer')) - except: - indexer = None - if showXML.findtext('tvdbid') != None: indexer_id = int(showXML.findtext('tvdbid')) elif showXML.findtext('id') != None: @@ -968,6 +963,18 @@ class GenericMetadata(): logger.log(u"Invalid Indexer ID (" + str(indexer_id) + "), not using metadata file", logger.WARNING) return empty_return + indexer = None + if showXML.findtext('indexer') != None: + indexer = int(showXML.findtext('indexer')) + elif 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: + indexer = 1 + elif 'tvrage' in epg_url: + indexer = 2 + + except Exception, e: logger.log( u"There was an error parsing your existing metadata file: '" + metadata_path + "' error: " + ex(e), diff --git a/sickbeard/metadata/kodi_12plus.py b/sickbeard/metadata/kodi_12plus.py index 1dba0d1730678ff7d099e39ee961a235ac39b91d..02d5b8735086229133f65fd4d269a9913b309cf2 100644 --- a/sickbeard/metadata/kodi_12plus.py +++ b/sickbeard/metadata/kodi_12plus.py @@ -153,11 +153,8 @@ class KODI_12PlusMetadata(generic.GenericMetadata): episodeguide = etree.SubElement(tv_node, "episodeguide") episodeguideurl = etree.SubElement(episodeguide, "url") - episodeguideurl2 = etree.SubElement(tv_node, "episodeguideurl") if getattr(myShow, 'id', None) is not None: - showurl = sickbeard.indexerApi(show_obj.indexer).config['base_url'] + str(myShow["id"]) + '/all/en.zip' - episodeguideurl.text = showurl - episodeguideurl2.text = showurl + episodeguideurl.text = sickbeard.indexerApi(show_obj.indexer).config['base_url'] + str(myShow["id"]) + '/all/en.zip' mpaa = etree.SubElement(tv_node, "mpaa") if getattr(myShow, 'contentrating', None) is not None: diff --git a/sickbeard/metadata/mede8er.py b/sickbeard/metadata/mede8er.py index 0fe31761a6395f2d933b4807ef5de940d3940778..4db891493c761c5f7d000d188d776f564807e4ab 100644 --- a/sickbeard/metadata/mede8er.py +++ b/sickbeard/metadata/mede8er.py @@ -390,7 +390,7 @@ class Mede8erMetadata(mediabrowser.MediaBrowserMetadata): nfo_file = ek.ek(open, nfo_file_path, 'w') - data.write(nfo_file, encoding="utf-8", xml_declaration=True) + data.write(nfo_file, encoding="UTF-8") nfo_file.close() helpers.chmodAsParent(nfo_file_path) except IOError, e: @@ -435,7 +435,7 @@ class Mede8erMetadata(mediabrowser.MediaBrowserMetadata): nfo_file = ek.ek(open, nfo_file_path, 'w') - data.write(nfo_file, encoding="utf-8", xml_declaration = True) + data.write(nfo_file, encoding="UTF-8") nfo_file.close() helpers.chmodAsParent(nfo_file_path) except IOError, e: diff --git a/sickbeard/notifiers/emailnotify.py b/sickbeard/notifiers/emailnotify.py index 02255ac4da3584884880032ba7ad2f6590516193..9b21d29bd8f2390873a646cbdb36f111105aa814 100644 --- a/sickbeard/notifiers/emailnotify.py +++ b/sickbeard/notifiers/emailnotify.py @@ -190,7 +190,13 @@ class EmailNotifier: def _sendmail(self, host, port, smtp_from, use_tls, user, pwd, to, msg, smtpDebug=False): logger.log('HOST: %s; PORT: %s; FROM: %s, TLS: %s, USER: %s, PWD: %s, TO: %s' % ( host, port, smtp_from, use_tls, user, pwd, to), logger.DEBUG) - srv = smtplib.SMTP(host, int(port)) + try: + srv = smtplib.SMTP(host, int(port)) + except Exception as e: + logger.log(u"Exception generated while sending e-mail: " + str(e), logger.ERROR) + logger.log(traceback.format_exc(), logger.DEBUG) + return False + if smtpDebug: srv.set_debuglevel(1) try: diff --git a/sickbeard/notifiers/kodi.py b/sickbeard/notifiers/kodi.py index 7b4bd3c6e991d6a92ef28be450fea5e815b427e6..f7e3b91689452f78eda1c28309c7026d444c2734 100644 --- a/sickbeard/notifiers/kodi.py +++ b/sickbeard/notifiers/kodi.py @@ -247,8 +247,8 @@ class KODINotifier: logger.log(u"KODI HTTP response: " + result.replace('\n', ''), logger.DEBUG) return result - except (urllib2.URLError, IOError), e: - logger.log(u"Warning: Couldn't contact KODI HTTP at " + ek.ss(url) + " " + ex(e), + except Exception as e: + logger.log(u"Warning: Couldn't contact KODI HTTP at " + ek.ss(url) + " " + str(e), logger.WARNING) return False diff --git a/sickbeard/notifiers/libnotify.py b/sickbeard/notifiers/libnotify.py index 9984bdce0901f84b6197c1f61371ce1237102a10..d4dc7953598ddf0851d7db3b3ecc36f84fc475c1 100644 --- a/sickbeard/notifiers/libnotify.py +++ b/sickbeard/notifiers/libnotify.py @@ -29,10 +29,11 @@ def diagnose(): user-readable message indicating possible issues. ''' try: - import pynotify #@UnusedImport + from gi.repository import Notify #@UnusedImport except ImportError: - return (u"<p>Error: pynotify isn't installed. On Ubuntu/Debian, install the " - u"<a href=\"apt:python-notify\">python-notify</a> package.") + return (u"<p>Error: gir-notify isn't installed. On Ubuntu/Debian, install the " + u"<a href=\"apt:gir1.2-notify-0.7\">gir1.2-notify-0.7</a> or " + u"<a href=\"apt:gir1.0-notify-0.4\">gir1.0-notify-0.4</a> package.") if 'DISPLAY' not in os.environ and 'DBUS_SESSION_BUS_ADDRESS' not in os.environ: return (u"<p>Error: Environment variables DISPLAY and DBUS_SESSION_BUS_ADDRESS " u"aren't set. libnotify will only work when you run SickRage " @@ -58,26 +59,26 @@ def diagnose(): class LibnotifyNotifier: def __init__(self): - self.pynotify = None + self.Notify = None self.gobject = None - def init_pynotify(self): - if self.pynotify is not None: + def init_notify(self): + if self.Notify is not None: return True try: - import pynotify + from gi.repository import Notify except ImportError: - logger.log(u"Unable to import pynotify. libnotify notifications won't work.", logger.ERROR) + logger.log(u"Unable to import Notify from gi.repository. libnotify notifications won't work.", logger.ERROR) return False try: import gobject except ImportError: logger.log(u"Unable to import gobject. We can't catch a GError in display.", logger.ERROR) return False - if not pynotify.init('SickRage'): - logger.log(u"Initialization of pynotify failed. libnotify notifications won't work.", logger.ERROR) + if not Notify.init('SickRage'): + logger.log(u"Initialization of Notify failed. libnotify notifications won't work.", logger.ERROR) return False - self.pynotify = pynotify + self.Notify = Notify self.gobject = gobject return True @@ -105,18 +106,17 @@ class LibnotifyNotifier: def _notify(self, title, message, force=False): if not sickbeard.USE_LIBNOTIFY and not force: return False - if not self.init_pynotify(): + if not self.init_notify(): return False # Can't make this a global constant because PROG_DIR isn't available # when the module is imported. - icon_path = os.path.join(sickbeard.PROG_DIR, "data/images/sickbeard_touch_icon.png") - icon_uri = 'file://' + os.path.abspath(icon_path) + icon_path = os.path.join(sickbeard.PROG_DIR, 'gui', 'slick', 'images', 'ico', 'favicon-120.png') # If the session bus can't be acquired here a bunch of warning messages # will be printed but the call to show() will still return True. # pynotify doesn't seem too keen on error handling. - n = self.pynotify.Notification(title, message, icon_uri) + n = self.Notify.Notification.new(title, message, icon_path) try: return n.show() except self.gobject.GError: diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py index 735469a93dd8b386b21bc30050846788f0164ee3..a878801854f85826193b8d74a59ced4060c9d7a7 100644 --- a/sickbeard/notifiers/plex.py +++ b/sickbeard/notifiers/plex.py @@ -19,41 +19,122 @@ import urllib import urllib2 import base64 +import re import sickbeard from sickbeard import logger from sickbeard import common from sickbeard.exceptions import ex -from sickbeard import encodingKludge as ek +from sickbeard.encodingKludge import fixStupidEncodings -from sickbeard.notifiers.kodi import KODINotifier +try: + import xml.etree.cElementTree as etree +except ImportError: + import elementtree.ElementTree as etree -# TODO: switch over to using ElementTree -from xml.dom import minidom +class PLEXNotifier: + + def _send_to_plex(self, command, host, username=None, password=None): + """Handles communication to Plex hosts via HTTP API + + Args: + command: Dictionary of field/data pairs, encoded via urllib and passed to the legacy xbmcCmds HTTP API + host: Plex host:port + username: Plex API username + password: Plex API password + + Returns: + Returns 'OK' for successful commands or False if there was an error + + """ -class PLEXNotifier(KODINotifier): - def _notify_pmc(self, message, title="SickRage", host=None, username=None, password=None, force=False): # fill in omitted parameters - if not host: - if sickbeard.PLEX_HOST: - host = sickbeard.PLEX_HOST # Use the default Plex host - else: - logger.log(u"No Plex host specified, check your settings", logger.DEBUG) - return False if not username: username = sickbeard.PLEX_USERNAME if not password: password = sickbeard.PLEX_PASSWORD + if not host: + logger.log(u'PLEX: No host specified, check your settings', logger.ERROR) + return False + + for key in command: + if type(command[key]) == unicode: + command[key] = command[key].encode('utf-8') + + enc_command = urllib.urlencode(command) + logger.log(u'PLEX: Encoded API command: ' + enc_command, logger.DEBUG) + + url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command) + try: + req = urllib2.Request(url) + # if we have a password, use authentication + if password: + base64string = base64.encodestring('%s:%s' % (username, password))[:-1] + authheader = 'Basic %s' % base64string + req.add_header('Authorization', authheader) + logger.log(u'PLEX: Contacting (with auth header) via url: ' + url, logger.DEBUG) + else: + logger.log(u'PLEX: Contacting via url: ' + url, logger.DEBUG) + + response = urllib2.urlopen(req) + + result = response.read().decode(sickbeard.SYS_ENCODING) + response.close() + + logger.log(u'PLEX: HTTP response: ' + result.replace('\n', ''), logger.DEBUG) + # could return result response = re.compile('<html><li>(.+\w)</html>').findall(result) + return 'OK' + + except (urllib2.URLError, IOError), e: + logger.log(u'PLEX: Warning: Couldn\'t contact Plex at ' + fixStupidEncodings(url) + ' ' + ex(e), logger.WARNING) + return False + + def _notify_pmc(self, message, title='SickRage', host=None, username=None, password=None, force=False): + """Internal wrapper for the notify_snatch and notify_download functions + + Args: + message: Message body of the notice to send + title: Title of the notice to send + host: Plex Media Client(s) host:port + username: Plex username + password: Plex password + force: Used for the Test method to override config safety checks + + Returns: + Returns a list results in the format of host:ip:result + The result will either be 'OK' or False, this is used to be parsed by the calling function. + + """ + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_PLEX and not force: - logger.log("Notification for Plex not enabled, skipping this notification", logger.DEBUG) return False - return self._notify_kodi(message=message, title=title, host=host, username=username, password=password, - force=True) + # fill in omitted parameters + if not host: + host = sickbeard.PLEX_HOST + if not username: + username = sickbeard.PLEX_USERNAME + if not password: + password = sickbeard.PLEX_PASSWORD + + result = '' + for curHost in [x.strip() for x in host.split(',')]: + logger.log(u'PLEX: Sending notification to \'%s\' - %s' % (curHost, message), logger.DEBUG) + + command = {'command': 'ExecBuiltIn', 'parameter': 'Notification(%s,%s)' % (title.encode('utf-8'), message.encode('utf-8'))} + notify_result = self._send_to_plex(command, curHost, username, password) + if notify_result: + result += '%s:%s' % (curHost, str(notify_result)) + + return result + +############################################################################## +# Public functions +############################################################################## def notify_snatch(self, ep_name): if sickbeard.PLEX_NOTIFY_ONSNATCH: @@ -65,62 +146,128 @@ class PLEXNotifier(KODINotifier): def notify_subtitle_download(self, ep_name, lang): if sickbeard.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD: - self._notify_pmc(ep_name + ": " + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD]) + self._notify_pmc(ep_name + ': ' + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD]) - def notify_git_update(self, new_version="??"): + def notify_git_update(self, new_version='??'): if sickbeard.USE_PLEX: update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] self._notify_pmc(update_text + new_version, title) - def test_notify(self, host, username, password): - return self._notify_pmc("Testing Plex notifications from SickRage", "Test Notification", host, username, - password, force=True) + def test_notify_pmc(self, host, username, password): + return self._notify_pmc('This is a test notification from SickRage', 'Test Notification', host, username, password, force=True) + + def test_notify_pms(self, host, username, password, plex_server_token): + return self.update_library(host=host, username=username, password=password, plex_server_token=plex_server_token, force=False) - def update_library(self, showName=None): + def update_library(self, ep_obj=None, host=None, username=None, password=None, plex_server_token=None, force=True): """Handles updating the Plex Media Server host via HTTP API Plex Media Server currently only supports updating the whole video library and not a specific path. Returns: - Returns True or False + Returns None for no issue, else a string of host with connection issues """ if sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY: + if not sickbeard.PLEX_SERVER_HOST: - logger.log(u"No Plex Media Server host specified, check your settings", logger.DEBUG) + logger.log(u'PLEX: No Plex Media Server host specified, check your settings', logger.DEBUG) return False - logger.log(u"Updating library for the Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST, - logger.INFO) + if not host: + host = sickbeard.PLEX_SERVER_HOST + if not username: + username = sickbeard.PLEX_USERNAME + if not password: + password = sickbeard.PLEX_PASSWORD + + if not plex_server_token: + 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 + 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='') + authheader = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))[:-1] + req.add_header('Authorization', authheader) + req.add_header('X-Plex-Device-Name', 'SickRage') + req.add_header('X-Plex-Product', 'SickRage Notifier') + req.add_header('X-Plex-Client-Identifier', sickbeard.CUR_COMMIT_HASH) + req.add_header('X-Plex-Version', '1.0') - url = "http://%s/library/sections" % sickbeard.PLEX_SERVER_HOST - if sickbeard.PLEX_SERVER_TOKEN: - url += "/?X-Plex-Token=" + sickbeard.PLEX_SERVER_TOKEN - try: - xml_sections = minidom.parse(urllib.urlopen(url)) - except IOError, e: - logger.log(u"Error while trying to contact Plex Media Server: " + ex(e), logger.ERROR) - return False + try: + response = urllib2.urlopen(req) + auth_tree = etree.parse(response) + token = auth_tree.findall('.//authentication-token')[0].text + token_arg = '?X-Plex-Token=' + token - sections = xml_sections.getElementsByTagName('Directory') - if not sections: - logger.log(u"Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.INFO) - return False + except urllib2.URLError as e: + logger.log(u'PLEX: Error fetching credentials from from plex.tv for user %s: %s' % (username, ex(e)), logger.DEBUG) + + except (ValueError, IndexError) as e: + logger.log(u'PLEX: Error parsing plex.tv response: ' + ex(e), logger.DEBUG) + + file_location = '' if None is ep_obj else ep_obj.location + host_list = [x.strip() for x in host.split(',')] + hosts_all = {} + hosts_match = {} + hosts_failed = [] + for cur_host in host_list: + + url = 'http://%s/library/sections%s' % (cur_host, token_arg) + try: + 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) + hosts_failed.append(cur_host) + continue + + sections = media_container.findall('.//Directory') + if not sections: + logger.log(u'PLEX: Plex Media Server not running on: ' + cur_host, logger.DEBUG) + hosts_failed.append(cur_host) + continue - for s in sections: - if s.getAttribute('type') == "show": - url = "http://%s/library/sections/%s/refresh" % (sickbeard.PLEX_SERVER_HOST, s.getAttribute('key')) - if sickbeard.PLEX_SERVER_TOKEN: - url += "/?X-Plex-Token=" + sickbeard.PLEX_SERVER_TOKEN - try: - urllib.urlopen(url) - except Exception, e: - logger.log(u"Error updating library section for Plex Media Server: " + ex(e), logger.ERROR) - return False + for section in sections: + if 'show' == section.attrib['type']: - return True + keyed_host = [(str(section.attrib['key']), cur_host)] + hosts_all.update(keyed_host) + if not file_location: + continue + + for section_location in section.findall('.//Location'): + section_path = re.sub(r'[/\\]+', '/', section_location.attrib['path'].lower()) + section_path = re.sub(r'^(.{,2})[/\\]', '', section_path) + location_path = re.sub(r'[/\\]+', '/', file_location.lower()) + location_path = re.sub(r'^(.{,2})[/\\]', '', location_path) + + if section_path in location_path: + hosts_match.update(keyed_host) + + hosts_try = (hosts_all.copy(), hosts_match.copy())[len(hosts_match)] + host_list = [] + for section_key, cur_host in hosts_try.items(): + + url = 'http://%s/library/sections/%s/refresh%s' % (cur_host, section_key, token_arg) + try: + 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) + hosts_failed.append(cur_host) + + if len(hosts_match): + logger.log(u'PLEX: Updating hosts where TV section paths match the downloaded show: ' + ', '.join(set(host_list)), logger.DEBUG) + else: + logger.log(u'PLEX: Updating all hosts with TV sections: ' + ', '.join(set(host_list)), logger.DEBUG) + return (', '.join(set(hosts_failed)), None)[not len(hosts_failed)] notifier = PLEXNotifier diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 173c47a2a7670793288b17c61055e5c083f3f16c..eede71fff326a0564381b4863b39490c26215555 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -183,10 +183,19 @@ class PostProcessor(object): # don't confuse glob with chars we didn't mean to use base_name = re.sub(r'[\[\]\*\?]', r'[\g<0>]', base_name) - if subfolders: - filelist = ek.ek(recursive_glob, ek.ek(os.path.dirname, file_path), base_name + '*') - else: - filelist = ek.ek(glob.glob, base_name + '*') + if subfolders: # subfolders are only checked in show folder, so names will always be exactly alike + filelist = ek.ek(recursive_glob, ek.ek(os.path.dirname, file_path), base_name + '*') # just create the list of all files starting with the basename + else: # this is called when PP, so we need to do the filename check case-insensitive + filelist = [] + checklist = ek.ek(glob.glob, ek.ek(os.path.join, ek.ek(os.path.dirname, file_path), '*')) # get a list of all the files in the folder + for filefound in checklist: # loop through all the files in the folder, and check if they are the same name even when the cases don't match + file_name = filefound.rpartition('.')[0] + if not base_name_only: + file_name = file_name + '.' + if file_name.lower() == base_name.lower(): # if there's no difference in the filename add it to the filelist + filelist.append(filefound) + + for associated_file_path in filelist: # only add associated to list if associated_file_path == file_path: @@ -201,7 +210,12 @@ class PostProcessor(object): if ek.ek(os.path.isfile, associated_file_path): file_path_list.append(associated_file_path) - + + if file_path_list: + self._log(u"Found the following associated files: " + str(file_path_list), logger.DEBUG) + else: + self._log(u"No associated files were during this pass", logger.DEBUG) + return file_path_list def _delete(self, file_path, associated_files=False): @@ -841,7 +855,7 @@ class PostProcessor(object): old_ep_status, old_ep_quality = common.Quality.splitCompositeStatus(ep_obj.status) # get the quality of the episode we're processing - if quality: + if quality and not common.Quality.qualityStrings[quality] == 'Unknown': self._log(u"Snatch history had a quality in it, using that: " + common.Quality.qualityStrings[quality], logger.DEBUG) new_ep_quality = quality diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index 02f150c0bf4b584a01ea64ad2913fe6384c33b96..453a34eb3b0836e4791abb12158d961789ea2ca1 100755 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -47,6 +47,7 @@ __all__ = ['ezrss', 'tntvillage', 'binsearch', 'eztv', + 'scenetime', ] import sickbeard diff --git a/sickbeard/providers/eztv.py b/sickbeard/providers/eztv.py index 909725c5daca12bfabd39607f701cbf221df3180..22a32f4eb3376fb9c8235b2ebcc34ec7424fcddc 100644 --- a/sickbeard/providers/eztv.py +++ b/sickbeard/providers/eztv.py @@ -16,7 +16,6 @@ # # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. - import traceback import re, datetime @@ -26,6 +25,7 @@ from sickbeard import classes from sickbeard import helpers from sickbeard import logger, tvcache, db from sickbeard.common import Quality +from sickbeard.bs4_parser import BS4Parser class EZTVProvider(generic.TorrentProvider): @@ -39,8 +39,9 @@ class EZTVProvider(generic.TorrentProvider): self.cache = EZTVCache(self) self.urls = { - 'base_url': 'http://eztvapi.re/', - 'show': 'http://eztvapi.re/show/%s', + 'base_url': 'https://eztv.ch/', + 'rss': 'https://eztv.ch/', + 'episode': 'http://eztvapi.re/show/%s', } self.url = self.urls['base_url'] @@ -68,12 +69,15 @@ class EZTVProvider(generic.TorrentProvider): return [search_string] def getQuality(self, item, anime=False): - if item.get('quality') == "480p": - return Quality.SDTV - elif item.get('quality') == "720p": - return Quality.HDWEBDL - elif item.get('quality') == "1080p": - return Quality.FULLHDWEBDL + if 'quality' in item: + if item.get('quality') == "480p": + return Quality.SDTV + elif item.get('quality') == "720p": + return Quality.HDWEBDL + elif item.get('quality') == "1080p": + return Quality.FULLHDWEBDL + else: + return Quality.sceneQuality(item.get('title'), anime) else: return Quality.sceneQuality(item.get('title'), anime) @@ -84,49 +88,83 @@ class EZTVProvider(generic.TorrentProvider): for mode in search_params.keys(): - if mode != 'Episode': - logger.log(u"" + self.name + " does not accept " + mode + " mode", logger.DEBUG) - return results + if mode == 'RSS': + for search_string in search_params[mode]: + searchURL = self.urls['rss'] + logger.log(u"" + self.name + " search page URL: " + searchURL, logger.DEBUG) - for search_string in search_params[mode]: + HTML = self.getURL(searchURL) + if not HTML: + logger.log(u"" + self.name + " could not retrieve page URL:" + searchURL, logger.DEBUG) + return results - searchURL = self.urls['show'] % (search_string['imdb_id']) - logger.log(u"" + self.name + " search page URL: " + searchURL, logger.DEBUG) + try: + with BS4Parser(HTML, features=["html5lib", "permissive"]) as parsedHTML: + resultsTable = parsedHTML.find_all('tr', attrs={'name': 'hover', 'class': 'header_brd'}) - try: - parsedJSON = self.getURL(searchURL, json=True) - except ValueError as e: - parsedJSON = None + if not resultsTable: + logger.log(u"The Data returned from " + self.name + " do not contains any torrent", + logger.DEBUG) + continue - if not parsedJSON: - logger.log(u"" + self.name + " could not retrieve page URL:" + searchURL, logger.DEBUG) - return results + for entries in resultsTable: + title = entries.find('a', attrs={'class': 'epinfo'}).contents[0] + link = entries.find('a', attrs={'class': 'magnet'}).get('href') - try: - for episode in parsedJSON['episodes']: - if int(episode.get('season')) == search_string.get('season') and \ - int(episode.get('episode')) == search_string.get('episode'): + item = { + 'title': title, + 'link': link, + } - for quality in episode['torrents'].keys(): - link = episode['torrents'][quality]['url'] - title = re.search('&dn=(.*?)&', link).group(1) + items[mode].append(item) - item = { - 'title': title, - 'link': link, - 'quality': quality - } + except Exception, e: + logger.log(u"Failed parsing " + self.name + " Traceback: " + traceback.format_exc(), + logger.ERROR) - # re.search in case of PROPER|REPACK. In other cases - # add_string is empty, so condition is met. - if re.search(search_string.get('add_string'), title): - items[mode].append(item) + elif mode == 'Episode': + for search_string in search_params[mode]: + searchURL = self.urls['episode'] % (search_string['imdb_id']) + logger.log(u"" + self.name + " search page URL: " + searchURL, logger.DEBUG) + + try: + parsedJSON = self.getURL(searchURL, json=True) + except ValueError as e: + parsedJSON = None + + if not parsedJSON: + logger.log(u"" + self.name + " could not retrieve page URL:" + searchURL, logger.DEBUG) + return results - break + try: + for episode in parsedJSON['episodes']: + if int(episode.get('season')) == search_string.get('season') and \ + int(episode.get('episode')) == search_string.get('episode'): - except Exception, e: - logger.log(u"Failed parsing " + self.name + " Traceback: " + traceback.format_exc(), - logger.ERROR) + for quality in episode['torrents'].keys(): + link = episode['torrents'][quality]['url'] + title = re.search('&dn=(.*?)&', link).group(1) + + item = { + 'title': title, + 'link': link, + 'quality': quality + } + + # re.search in case of PROPER|REPACK. In other cases + # add_string is empty, so condition is met. + if 'add_string' in search_string and re.search(search_string.get('add_string'), title): + items[mode].append(item) + + break + + except Exception, e: + logger.log(u"Failed parsing " + self.name + " Traceback: " + traceback.format_exc(), + logger.ERROR) + + else: + logger.log(u"" + self.name + " does not accept " + mode + " mode", logger.DEBUG) + return results results += items[mode] diff --git a/sickbeard/providers/iptorrents.py b/sickbeard/providers/iptorrents.py index 1af7e8ca8008e926371ea5fde8efaffc9990c2c6..28a8f3c1038d54d0de6a39f96a20a62738811b53 100644 --- a/sickbeard/providers/iptorrents.py +++ b/sickbeard/providers/iptorrents.py @@ -24,6 +24,7 @@ import itertools import sickbeard import generic +from sickbeard.common import MULTI_EP_RESULT, SEASON_RESULT from sickbeard.common import Quality from sickbeard import logger from sickbeard import tvcache @@ -59,12 +60,12 @@ class IPTorrentsProvider(generic.TorrentProvider): self.urls = {'base_url': 'https://iptorrents.eu', 'login': 'https://iptorrents.eu/torrents/', - 'search': 'https://iptorrents.eu/torrents/?%s%s&q=%s&qf=ti', + 'search': 'https://iptorrents.eu/t?%s%s&q=%s&qf=#torrents', } self.url = self.urls['base_url'] - self.categorie = 'l73=1&l78=1&l66=1&l65=1&l79=1&l5=1&l4=1' + self.categorie = 'l73=' def isEnabled(self): return self.enabled diff --git a/sickbeard/providers/rarbg.py b/sickbeard/providers/rarbg.py index ad8e63d6a7e7302c4b8b8d859b82f48ded7c395f..c56a3ccc672cdc691632c35a66fd7bb5312fc01c 100644 --- a/sickbeard/providers/rarbg.py +++ b/sickbeard/providers/rarbg.py @@ -30,7 +30,7 @@ from lib import requests from lib.requests import exceptions import sickbeard -from sickbeard.common import Quality +from sickbeard.common import Quality, USER_AGENT from sickbeard import logger from sickbeard import tvcache from sickbeard import show_name_helpers @@ -62,13 +62,13 @@ class RarbgProvider(generic.TorrentProvider): self.token = None self.tokenExpireDate = None - self.urls = {'url': 'https://rarbg.com', - 'token': 'https://torrentapi.org/pubapi.php?get_token=get_token&format=json', - 'listing': 'https://torrentapi.org/pubapi.php?mode=list', - 'search': 'https://torrentapi.org/pubapi.php?mode=search&search_string={search_string}', - 'search_tvdb': 'https://torrentapi.org/pubapi.php?mode=search&search_tvdb={tvdb}&search_string={search_string}', - 'search_tvrage': 'https://torrentapi.org/pubapi.php?mode=search&search_tvrage={tvrage}&search_string={search_string}', - 'api_spec': 'https://rarbg.com/pubapi/apidocs.txt', + self.urls = {'url': u'https://rarbg.com', + 'token': u'https://torrentapi.org/pubapi.php?get_token=get_token&format=json', + 'listing': u'https://torrentapi.org/pubapi.php?mode=list', + 'search': u'https://torrentapi.org/pubapi.php?mode=search&search_string={search_string}', + 'search_tvdb': u'https://torrentapi.org/pubapi.php?mode=search&search_tvdb={tvdb}&search_string={search_string}', + 'search_tvrage': u'https://torrentapi.org/pubapi.php?mode=search&search_tvrage={tvrage}&search_string={search_string}', + 'api_spec': u'https://rarbg.com/pubapi/apidocs.txt', } self.url = self.urls['listing'] @@ -92,6 +92,8 @@ class RarbgProvider(generic.TorrentProvider): self.next_request = datetime.datetime.now() self.cache = RarbgCache(self) + + self.headers = {'User-Agent': USER_AGENT} def isEnabled(self): return self.enabled @@ -107,7 +109,7 @@ class RarbgProvider(generic.TorrentProvider): resp_json = None try: - response = self.session.get(self.urls['token'], timeout=30, verify=False) + response = self.session.get(self.urls['token'], timeout=30, verify=False, headers=self.headers) response.raise_for_status() resp_json = response.json() except RequestException as e: @@ -204,18 +206,18 @@ class RarbgProvider(generic.TorrentProvider): searchURL = self.urls['listing'] + self.defaultOptions elif mode == 'Season': if ep_indexer == INDEXER_TVDB: - searchURL = self.urls['search_tvdb'].format(search_string=urllib.quote(search_string), tvdb=ep_indexerid) + self.defaultOptions + searchURL = self.urls['search_tvdb'].format(search_string=search_string, tvdb=ep_indexerid) + self.defaultOptions elif ep_indexer == INDEXER_TVRAGE: - searchURL = self.urls['search_tvrage'].format(search_string=urllib.quote(search_string), tvrage=ep_indexerid) + self.defaultOptions + searchURL = self.urls['search_tvrage'].format(search_string=search_string, tvrage=ep_indexerid) + self.defaultOptions else: - searchURL = self.urls['search'].format(search_string=urllib.quote(search_string)) + self.defaultOptions + searchURL = self.urls['search'].format(search_string=search_string) + self.defaultOptions elif mode == 'Episode': if ep_indexer == INDEXER_TVDB: - searchURL = self.urls['search_tvdb'].format(search_string=urllib.quote(search_string), tvdb=ep_indexerid) + self.defaultOptions + searchURL = self.urls['search_tvdb'].format(search_string=search_string, tvdb=ep_indexerid) + self.defaultOptions elif ep_indexer == INDEXER_TVRAGE: - searchURL = self.urls['search_tvrage'].format(search_string=urllib.quote(search_string), tvrage=ep_indexerid) + self.defaultOptions + searchURL = self.urls['search_tvrage'].format(search_string=search_string, tvrage=ep_indexerid) + self.defaultOptions else: - searchURL = self.urls['search'].format(search_string=urllib.quote(search_string)) + self.defaultOptions + searchURL = self.urls['search'].format(search_string=search_string) + self.defaultOptions else: logger.log(u'{name} invalid search mode:{mode}'.format(name=self.name, mode=mode), logger.ERROR) diff --git a/sickbeard/providers/scenetime.py b/sickbeard/providers/scenetime.py new file mode 100644 index 0000000000000000000000000000000000000000..c3aaca9d341022e5f457e145904e382f3f3f0970 --- /dev/null +++ b/sickbeard/providers/scenetime.py @@ -0,0 +1,285 @@ +# Author: Idan Gutman +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import re +import traceback +import datetime +import urlparse +import sickbeard +import generic +import urllib +from sickbeard.common import Quality +from sickbeard import logger +from sickbeard import tvcache +from sickbeard import db +from sickbeard import classes +from sickbeard import helpers +from sickbeard import show_name_helpers +from sickbeard.exceptions import ex +from sickbeard import clients +from lib import requests +from lib.requests import exceptions +from sickbeard.bs4_parser import BS4Parser +from lib.unidecode import unidecode +from sickbeard.helpers import sanitizeSceneName + + +class SceneTimeProvider(generic.TorrentProvider): + + def __init__(self): + + generic.TorrentProvider.__init__(self, "SceneTime") + + self.supportsBacklog = True + + self.enabled = False + self.username = None + self.password = None + self.ratio = None + self.minseed = None + self.minleech = None + + self.cache = SceneTimeCache(self) + + self.urls = {'base_url': 'https://www.scenetime.com', + 'login': 'https://www.scenetime.com/takelogin.php', + 'detail': 'https://www.scenetime.com/details.php?id=%s', + 'search': 'https://www.scenetime.com/browse.php?search=%s%s', + 'download': 'https://www.scenetime.com/download.php/%s/%s', + } + + self.url = self.urls['base_url'] + + self.categories = "&c2=1&c43=13&c9=1&c63=1&c77=1&c79=1&c100=1&c101=1" + + def isEnabled(self): + return self.enabled + + def imageName(self): + return 'scenetime.png' + + def getQuality(self, item, anime=False): + + quality = Quality.sceneQuality(item[0], anime) + return quality + + def _doLogin(self): + + login_params = {'username': self.username, + 'password': self.password + } + + self.session = requests.Session() + + try: + response = self.session.post(self.urls['login'], data=login_params, timeout=30, verify=False) + except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError), e: + logger.log(u'Unable to connect to ' + self.name + ' provider: ' + ex(e), logger.ERROR) + return False + + if re.search('Username or password incorrect', response.text): + logger.log(u'Invalid username or password for ' + self.name + ' Check your settings', logger.ERROR) + return False + + return True + + def _get_season_search_strings(self, ep_obj): + + search_string = {'Season': []} + for show_name in set(show_name_helpers.allPossibleShowNames(self.show)): + if ep_obj.show.air_by_date or ep_obj.show.sports: + ep_string = show_name + '.' + str(ep_obj.airdate).split('-')[0] + elif ep_obj.show.anime: + ep_string = show_name + '.' + "%d" % ep_obj.scene_absolute_number + else: + ep_string = show_name + '.S%02d' % int(ep_obj.scene_season) #1) showName SXX + + search_string['Season'].append(ep_string) + + return [search_string] + + def _get_episode_search_strings(self, ep_obj, add_string=''): + + search_string = {'Episode': []} + + if not ep_obj: + return [] + + if self.show.air_by_date: + for show_name in set(show_name_helpers.allPossibleShowNames(self.show)): + ep_string = sanitizeSceneName(show_name) + ' ' + \ + str(ep_obj.airdate).replace('-', '|') + search_string['Episode'].append(ep_string) + elif self.show.sports: + for show_name in set(show_name_helpers.allPossibleShowNames(self.show)): + ep_string = sanitizeSceneName(show_name) + ' ' + \ + str(ep_obj.airdate).replace('-', '|') + '|' + \ + ep_obj.airdate.strftime('%b') + search_string['Episode'].append(ep_string) + elif self.show.anime: + for show_name in set(show_name_helpers.allPossibleShowNames(self.show)): + ep_string = sanitizeSceneName(show_name) + ' ' + \ + "%i" % int(ep_obj.scene_absolute_number) + search_string['Episode'].append(ep_string) + else: + 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} + ' %s' % add_string + + search_string['Episode'].append(re.sub('\s+', ' ', ep_string)) + + return [search_string] + + def _doSearch(self, search_params, search_mode='eponly', epcount=0, age=0, epObj=None): + + results = [] + items = {'Season': [], 'Episode': [], 'RSS': []} + + if not self._doLogin(): + return results + + for mode in search_params.keys(): + for search_string in search_params[mode]: + + if isinstance(search_string, unicode): + search_string = unidecode(search_string) + + searchURL = self.urls['search'] % (urllib.quote(search_string), self.categories) + + logger.log(u"Search string: " + searchURL, logger.DEBUG) + + data = self.getURL(searchURL) + if not data: + continue + + try: + with BS4Parser(data, features=["html5lib", "permissive"]) as html: + torrent_table = html.select("#torrenttable table"); + torrent_rows = torrent_table[0].select("tr") if torrent_table else [] + + #Continue only if one Release is found + if len(torrent_rows) < 2: + logger.log(u"The Data returned from %s does not contain any torrent links" % self.name, + logger.DEBUG) + continue + + for result in torrent_rows[1:]: + cells = result.find_all('td') + + link = cells[1].find('a'); + + full_id = link['href'].replace('details.php?id=', '') + torrent_id = full_id.split("&")[0] + + try: + title = link.contents[0].get_text() + + filename = "%s.torrent" % title.replace(" ", ".") + + download_url = self.urls['download'] % (torrent_id, filename) + + id = int(torrent_id) + seeders = int(cells[6].get_text()) + leechers = int(cells[7].get_text()) + except (AttributeError, TypeError): + continue + + #Filter unseeded torrent + if mode != 'RSS' and (seeders < self.minseed or leechers < self.minleech): + continue + + if not title or not download_url: + continue + + item = title, download_url, id, seeders, leechers + logger.log(u"Found result: " + title.replace(' ','.') + " (" + searchURL + ")", logger.DEBUG) + + items[mode].append(item) + + except Exception, e: + logger.log(u"Failed parsing " + self.name + " Traceback: " + traceback.format_exc(), logger.ERROR) + + #For each search mode sort all the items by seeders + items[mode].sort(key=lambda tup: tup[3], reverse=True) + + results += items[mode] + + return results + + def _get_title_and_url(self, item): + + title, url, id, seeders, leechers = item + + if title: + title = u'' + title + title = title.replace(' ', '.') + title = self._clean_title_from_provider(title) + + if url: + url = str(url).replace('&', '&') + + return (title, url) + + def findPropers(self, search_date=datetime.datetime.today()): + + results = [] + + myDB = db.DBConnection() + sqlResults = myDB.select( + 'SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.airdate FROM tv_episodes AS e' + + ' INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id)' + + ' WHERE e.airdate >= ' + str(search_date.toordinal()) + + ' AND (e.status IN (' + ','.join([str(x) for x in Quality.DOWNLOADED]) + ')' + + ' OR (e.status IN (' + ','.join([str(x) for x in Quality.SNATCHED]) + ')))' + ) + + if not sqlResults: + return [] + + for sqlshow in sqlResults: + self.show = helpers.findCertainShow(sickbeard.showList, int(sqlshow["showid"])) + if self.show: + curEp = self.show.getEpisode(int(sqlshow["season"]), int(sqlshow["episode"])) + + searchString = self._get_episode_search_strings(curEp, add_string='PROPER|REPACK') + + for item in self._doSearch(searchString[0]): + title, url = self._get_title_and_url(item) + results.append(classes.Proper(title, url, datetime.datetime.today(), self.show)) + + return results + + def seedRatio(self): + return self.ratio + + +class SceneTimeCache(tvcache.TVCache): + def __init__(self, provider): + + tvcache.TVCache.__init__(self, provider) + + # only poll SceneTime every 20 minutes max + self.minTime = 20 + + def _getRSSData(self): + search_params = {'RSS': ['']} + return {'entries': self.provider._doSearch(search_params)} + + +provider = SceneTimeProvider() diff --git a/sickbeard/showUpdater.py b/sickbeard/showUpdater.py index a5dd91550afc7e3e46dcbc20acd76b68c0813ea4..6ceeefcd69bf3ab467dd4bdb32aafc1305b5e348 100644 --- a/sickbeard/showUpdater.py +++ b/sickbeard/showUpdater.py @@ -78,16 +78,14 @@ class ShowUpdater(): # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update, otherwise just refresh if curShow.should_update(update_date=update_date) or curShow.indexerid in stale_should_update: try: - curQueueItem = sickbeard.showQueueScheduler.action.updateShow(curShow, True) # @UndefinedVariable + piList.append(sickbeard.showQueueScheduler.action.updateShow(curShow, True)) # @UndefinedVariable except exceptions.CantUpdateException as e: logger.log("Unable to update show: {0}".format(str(e)),logger.DEBUG) else: logger.log( u"Not updating episodes for show " + curShow.name + " because it's marked as ended and last/next episode is not within the grace period.", logger.DEBUG) - curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(curShow, True) # @UndefinedVariable - - piList.append(curQueueItem) + piList.append(sickbeard.showQueueScheduler.action.refreshShow(curShow, True)) # @UndefinedVariable except (exceptions.CantUpdateException, exceptions.CantRefreshException), e: logger.log(u"Automatic update failed: " + ex(e), logger.ERROR) diff --git a/sickbeard/tv.py b/sickbeard/tv.py index 512893fadf542c5852713d3cee4850e229b7e837..128a2eba4f8d6166a18107e544b71d6ee617784d 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -1437,7 +1437,8 @@ class TVEpisode(object): for subtitle in subtitles.get(video): added_subtitles.append(subtitle.language.alpha2) helpers.chmodAsParent(subtitle.path) - + except ServiceError as e: + logger.log("Service is unavailable: {0}".format(str(e)), logger.INFO) except Exception as e: logger.log("Error occurred when downloading subtitles: " + str(e), logger.ERROR) return diff --git a/sickbeard/tvcache.py b/sickbeard/tvcache.py index 4d642472ac861aea7fe68983df916331b43dc3b3..3e2804cba01b83d6181ddcdda6d9b1a3af9bd3a9 100644 --- a/sickbeard/tvcache.py +++ b/sickbeard/tvcache.py @@ -299,7 +299,7 @@ class TVCache(): def searchCache(self, episode, manualSearch=False, downCurQuality=False): neededEps = self.findNeededEpisodes(episode, manualSearch, downCurQuality) - return neededEps[episode] if len(neededEps) > 0 else [] + return neededEps[episode] if episode in neededEps else [] def listPropers(self, date=None, delimiter="."): myDB = self._getDB() diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index d373be2228159b237f5f089f881168b13c157556..fc8bfcbd6e3139723669b6623655d828a33ec094 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -287,6 +287,9 @@ class LoginHandler(BaseHandler): if api_key: remember_me = int(self.get_argument('remember_me', default=0) or 0) self.set_secure_cookie('sickrage_user', api_key, expires_days=30 if remember_me > 0 else None) + logger.log('User logged into the SickRage web interface from IP: ' + self.request.remote_ip, logger.INFO) + else: + logger.log('User attempted a failed login to the SickRage web interface from IP: ' + self.request.remote_ip, logger.WARNING) self.redirect('/home/') @@ -891,21 +894,44 @@ class Home(WebRoot): return finalResult - def testPLEX(self, host=None, username=None, password=None): - # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') + def testPMC(self, host=None, username=None, password=None): + 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 finalResult = '' - for curHost in [x.strip() for x in host.split(",")]: - curResult = notifiers.plex_notifier.test_notify(urllib.unquote_plus(curHost), username, password) - if len(curResult.split(":")) > 2 and 'OK' in curResult.split(":")[2]: - finalResult += "Test Plex notice sent successfully to " + urllib.unquote_plus(curHost) + for curHost in [x.strip() for x in host.split(',')]: + curResult = notifiers.plex_notifier.test_notify_pmc(urllib.unquote_plus(curHost), username, password) + if len(curResult.split(':')) > 2 and 'OK' in curResult.split(':')[2]: + finalResult += 'Successful test notice sent to Plex client ... ' + urllib.unquote_plus(curHost) else: - finalResult += "Test Plex notice failed to " + urllib.unquote_plus(curHost) - finalResult += "<br />\n" + finalResult += 'Test failed for Plex client ... ' + urllib.unquote_plus(curHost) + finalResult += '<br />' + '\n' + + ui.notifications.message('Tested Plex client(s): ', urllib.unquote_plus(host.replace(',', ', '))) return finalResult + def testPMS(self, host=None, username=None, password=None, plex_server_token=None): + 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 + + finalResult = '' + + curResult = notifiers.plex_notifier.test_notify_pms(urllib.unquote_plus(host), username, password, plex_server_token) + if None is curResult: + finalResult += 'Successful test of Plex server(s) ... ' + urllib.unquote_plus(host.replace(',', ', ')) + else: + finalResult += 'Test failed for Plex server(s) ... ' + urllib.unquote_plus(curResult.replace(',', ', ')) + finalResult += '<br />' + '\n' + + ui.notifications.message('Tested Plex Media Server host(s): ', urllib.unquote_plus(host.replace(',', ', '))) + + return finalResult + def testLibnotify(self): # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') @@ -960,8 +986,7 @@ class Home(WebRoot): else: return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % { "dbloc": dbloc} - - + def testTrakt(self, username=None, password=None, disable_ssl=None, blacklist_name=None): # self.set_header('Cache-Control', 'max-age=0,no-cache,no-store') if disable_ssl == 'true': @@ -1183,14 +1208,18 @@ class Home(WebRoot): if not sickbeard.showQueueScheduler.action.isBeingAdded(showObj): if not sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): + if showObj.paused: + t.submenu.append({'title': 'Resume', 'path': 'home/togglePause?show=%d' % showObj.indexerid}) + else: + t.submenu.append({'title': 'Pause', 'path': 'home/togglePause?show=%d' % showObj.indexerid}) + t.submenu.append( {'title': 'Remove', 'path': 'home/deleteShow?show=%d' % showObj.indexerid, 'confirm': True}) t.submenu.append({'title': 'Re-scan files', 'path': 'home/refreshShow?show=%d' % showObj.indexerid}) t.submenu.append( {'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&force=1' % showObj.indexerid}) t.submenu.append({'title': 'Update show in KODI', - 'path': 'home/updateKODI?showName=%s' % urllib.quote_plus( - showObj.name.encode('utf-8')), 'requires': self.haveKODI}) + 'path': 'home/updateKODI?show=%d' % showObj.indexerid, 'requires': self.haveKODI}) t.submenu.append({'title': 'Preview Rename', 'path': 'home/testRename?show=%d' % showObj.indexerid}) if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled( showObj) and showObj.subtitles: @@ -1516,7 +1545,7 @@ class Home(WebRoot): if not paused and (sickbeard.TRAKT_USE_ROLLING_DOWNLOAD and sickbeard.USE_TRAKT): # Checking if trakt and rolling_download are enable because updateWantedList() # doesn't do the distinction between a failuire and being not activated(Return false) - if not sickbeard.traktRollingScheduler.action.updateWantedList(): + if not sickbeard.traktRollingScheduler.action.updateWantedList(showObj.indexerid): errors.append("Unable to force an update on wanted episode") if do_update_scene_numbering: @@ -1536,6 +1565,31 @@ class Home(WebRoot): return self.redirect("/home/displayShow?show=" + show) + def togglePause(self, show=None): + if show is None: + return self._genericMessage("Error", "Invalid show ID") + + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + + if showObj is None: + return self._genericMessage("Error", "Unable to find the specified show") + + if showObj.paused: + showObj.paused = 0 + else: + showObj.paused = 1 + + showObj.saveToDB() + + if not showObj.paused and sickbeard.TRAKT_USE_ROLLING_DOWNLOAD and sickbeard.USE_TRAKT: + # Checking if trakt and rolling_download are enable because updateWantedList() + # doesn't do the distinction between a failuire and being not activated(Return false) + if not sickbeard.traktRollingScheduler.action.updateWantedList(showObj.indexerid): + errors.append("Unable to force an update on wanted episode") + + ui.notifications.message('<b>%s</b> has been %s' % (showObj.name,('resumed', 'paused')[showObj.paused])) + return self.redirect("/home/displayShow?show=" + show) + def deleteShow(self, show=None, full=0): if show is None: @@ -1628,8 +1682,14 @@ class Home(WebRoot): return self.redirect("/home/displayShow?show=" + str(showObj.indexerid)) - def updateKODI(self, showName=None): - + def updateKODI(self, show=None): + showName=None + + if show: + showObj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + if showObj: + showName=urllib.quote_plus(showObj.name.encode('utf-8')) + # only send update to first host in the list -- workaround for kodi sql backend users if sickbeard.KODI_UPDATE_ONLYFIRST: # only send update to first host in the list -- workaround for kodi sql backend users @@ -1641,8 +1701,11 @@ class Home(WebRoot): ui.notifications.message("Library update command sent to KODI host(s): " + host) else: ui.notifications.error("Unable to contact one or more KODI host(s): " + host) - return self.redirect('/home/') - + + if showObj: + return self.redirect('/home/displayShow?show=' + str(showObj.indexerid)) + else: + return self.redirect('/home/') def updatePLEX(self): if notifiers.plex_notifier.update_library(): @@ -1914,7 +1977,11 @@ class Home(WebRoot): def getEpisodes(searchThread, searchstatus): results = [] 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) + return results + if isinstance(searchThread, sickbeard.search_queue.ManualSearchQueueItem): results.append({'show': searchThread.show.indexerid, 'episode': searchThread.segment.episode, @@ -3643,7 +3710,7 @@ class ConfigGeneral(Config): handle_reverse_proxy=None, sort_article=None, auto_update=None, notify_on_update=None, proxy_setting=None, proxy_indexers=None, anon_redirect=None, git_path=None, git_remote=None, calendar_unprotected=None, debug=None, no_restart=None, coming_eps_missed_range=None, - display_filesize=None, filter_row=None, fuzzy_dating=None, trim_zero=None, date_preset=None, date_preset_na=None, time_preset=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): @@ -3694,7 +3761,6 @@ class ConfigGeneral(Config): sickbeard.WEB_USERNAME = web_username sickbeard.WEB_PASSWORD = web_password - sickbeard.DISPLAY_FILESIZE = config.checkbox_to_value(display_filesize) sickbeard.FILTER_ROW = config.checkbox_to_value(filter_row) sickbeard.FUZZY_DATING = config.checkbox_to_value(fuzzy_dating) sickbeard.TRIM_ZERO = config.checkbox_to_value(trim_zero) @@ -3854,8 +3920,7 @@ class ConfigSearch(Config): sickbeard.RANDOMIZE_PROVIDERS = config.checkbox_to_value(randomize_providers) - sickbeard.DOWNLOAD_PROPERS = config.checkbox_to_value(download_propers) - config.change_DOWNLOAD_PROPERS(sickbeard.DOWNLOAD_PROPERS) + config.change_DOWNLOAD_PROPERS(download_propers) sickbeard.CHECK_PROPERS_INTERVAL = check_propers_interval @@ -4984,10 +5049,6 @@ class ErrorLogs(WebRoot): with ek.ek(codecs.open, *[logger.logFile + "." + str(i), 'r', 'utf-8']) as f: data += Get_Data(minLevel, f.readlines(), len(data), regex, logFilter, logSearch, maxLines) - - - - result = "".join(data) t.logLines = result