Add Search Queue Overview page

* Add expandable search queue details on the Manage Searches page
* Fix failed status episodes not included in next_episode search function
* Change prevent another show update from running if one is already running
* Change split Force backlog button on the Manage Searches page into: Force Limited, Force Full
* Change refactor properFinder to be part of the search
* Change improve threading of generic_queue, show_queue and search_queue
* Change disable the Force buttons on the Manage Searches page while a search is running
* Change disable the Pause buttons on the Manage Searches page if a search is not running
* Change scheduler forceRun
* Change staggered periods of testing and updating of all shows "ended" status up to 460 days
This commit is contained in:
Prinz23 2015-05-04 21:14:29 +02:00
parent f854e9cab5
commit 2e711423b9
21 changed files with 1112 additions and 613 deletions

View file

@ -1,4 +1,4 @@
### 0.x.x (2015-xx-xx xx:xx:xx UTC) ### 0.x.x (2015-xx-xx xx:xx:xx UTC)
* Update Tornado webserver to 4.2.dev1 (609dbb9) * Update Tornado webserver to 4.2.dev1 (609dbb9)
* Update change to suppress reporting of Tornado exception error 1 to updated package as listed in hacks.txt * Update change to suppress reporting of Tornado exception error 1 to updated package as listed in hacks.txt
@ -49,6 +49,16 @@
* Add clarity to the output of a successful post process but with some issues rather than "there were problems" * Add clarity to the output of a successful post process but with some issues rather than "there were problems"
* Add a conclusive bottom line to the pp result report * Add a conclusive bottom line to the pp result report
* Change helpers doctests to unittests * Change helpers doctests to unittests
* Add Search Queue Overview page
* Add expandable search queue details on the Manage Searches page
* Fix failed status episodes not included in next_episode search function
* Change prevent another show update from running if one is already running
* Change split Force backlog button on the Manage Searches page into: Force Limited, Force Full
* Change refactor properFinder to be part of the search
* Change improve threading of generic_queue, show_queue and search_queue
* Change disable the Force buttons on the Manage Searches page while a search is running
* Change disable the Pause buttons on the Manage Searches page if a search is not running
* Change staggered periods of testing and updating of all shows "ended" status up to 460 days
[develop changelog] [develop changelog]
* Fix issue, when adding existing shows, set its default group to ensure it now appears on the show list page * Fix issue, when adding existing shows, set its default group to ensure it now appears on the show list page

View file

@ -442,6 +442,7 @@ inc_top.tmpl
content:"\e613" content:"\e613"
} }
.sgicon-showqueue:before,
.sgicon-refresh:before{ .sgicon-refresh:before{
content:"\e614" content:"\e614"
} }

View file

@ -102,6 +102,7 @@
$('#SubMenu a:contains("Processing")').addClass('btn').html('<i class="sgicon-postprocess"></i>Post-Processing'); $('#SubMenu a:contains("Processing")').addClass('btn').html('<i class="sgicon-postprocess"></i>Post-Processing');
$('#SubMenu a:contains("Manage Searches")').addClass('btn').html('<i class="sgicon-search"></i>Manage Searches'); $('#SubMenu a:contains("Manage Searches")').addClass('btn').html('<i class="sgicon-search"></i>Manage Searches');
$('#SubMenu a:contains("Manage Torrents")').addClass('btn').html('<i class="sgicon-bittorrent"></i>Manage Torrents'); $('#SubMenu a:contains("Manage Torrents")').addClass('btn').html('<i class="sgicon-bittorrent"></i>Manage Torrents');
$('#SubMenu a:contains("Show Queue Overview")').addClass('btn').html('<i class="sgicon-showqueue"></i>Show Queue Overview');
$('#SubMenu a[href$="/manage/failedDownloads/"]').addClass('btn').html('<i class="sgicon-failed"></i>Failed Downloads'); $('#SubMenu a[href$="/manage/failedDownloads/"]').addClass('btn').html('<i class="sgicon-failed"></i>Failed Downloads');
$('#SubMenu a:contains("Notification")').addClass('btn').html('<i class="sgicon-notification"></i>Notifications'); $('#SubMenu a:contains("Notification")').addClass('btn').html('<i class="sgicon-notification"></i>Notifications');
$('#SubMenu a:contains("Update show in XBMC")').addClass('btn').html('<i class="sgicon-xbmc"></i>Update show in XBMC'); $('#SubMenu a:contains("Update show in XBMC")').addClass('btn').html('<i class="sgicon-xbmc"></i>Update show in XBMC');
@ -166,6 +167,7 @@
<li><a href="$sbRoot/manage/" tabindex="$tab#set $tab += 1#"><i class="sgicon-massupdate"></i>Mass Update</a></li> <li><a href="$sbRoot/manage/" tabindex="$tab#set $tab += 1#"><i class="sgicon-massupdate"></i>Mass Update</a></li>
<li><a href="$sbRoot/manage/backlogOverview/" tabindex="$tab#set $tab += 1#"><i class="sgicon-backlog"></i>Backlog Overview</a></li> <li><a href="$sbRoot/manage/backlogOverview/" tabindex="$tab#set $tab += 1#"><i class="sgicon-backlog"></i>Backlog Overview</a></li>
<li><a href="$sbRoot/manage/manageSearches/" tabindex="$tab#set $tab += 1#"><i class="sgicon-search"></i>Manage Searches</a></li> <li><a href="$sbRoot/manage/manageSearches/" tabindex="$tab#set $tab += 1#"><i class="sgicon-search"></i>Manage Searches</a></li>
<li><a href="$sbRoot/manage/showQueueOverview/" tabindex="$tab#set $tab += 1#"><i class="sgicon-showqueue"></i>Show Queue Overview</a></li>
<li><a href="$sbRoot/manage/episodeStatuses/" tabindex="$tab#set $tab += 1#"><i class="sgicon-episodestatus"></i>Episode Status Management</a></li> <li><a href="$sbRoot/manage/episodeStatuses/" tabindex="$tab#set $tab += 1#"><i class="sgicon-episodestatus"></i>Episode Status Management</a></li>
#if $sickbeard.USE_PLEX and $sickbeard.PLEX_SERVER_HOST != '' #if $sickbeard.USE_PLEX and $sickbeard.PLEX_SERVER_HOST != ''
<li><a href="$sbRoot/home/updatePLEX/" tabindex="$tab#set $tab += 1#"><i class="sgicon-plex"></i>Update PLEX</a></li> <li><a href="$sbRoot/home/updatePLEX/" tabindex="$tab#set $tab += 1#"><i class="sgicon-plex"></i>Update PLEX</a></li>

View file

@ -1,6 +1,4 @@
#import sickbeard #import sickbeard
#import datetime
#from sickbeard.common import *
## ##
#set global $title = 'Manage Searches' #set global $title = 'Manage Searches'
#set global $header = 'Manage Searches' #set global $header = 'Manage Searches'
@ -11,6 +9,7 @@
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/manageSearches.js?$sbPID"></script>
<div id="content800"> <div id="content800">
#if $varExists('header') #if $varExists('header')
<h1 class="header">$header</h1> <h1 class="header">$header</h1>
@ -20,18 +19,19 @@
<div id="summary2" class="align-left"> <div id="summary2" class="align-left">
<h3>Backlog Search:</h3> <h3>Backlog Search:</h3>
<a class="btn" href="$sbRoot/manage/manageSearches/forceBacklog"><i class="sgicon-play"></i> Force</a> <a id="forcebacklog" class="btn#if $backlogRunning# disabled#end if#" href="$sbRoot/manage/manageSearches/forceLimitedBacklog"><i class="sgicon-play"></i> Force Limited</a>
<a class="btn" href="$sbRoot/manage/manageSearches/pauseBacklog?paused=#if $backlogPaused then '0' else '1'#"><i class="#if $backlogPaused then 'sgicon-play' else 'sgicon-pause'#"></i> #if $backlogPaused then 'Unpause' else 'Pause'#</a> <a id="forcefullbacklog" class="btn#if $backlogRunning# disabled#end if#" href="$sbRoot/manage/manageSearches/forceFullBacklog"><i class="sgicon-play"></i> Force Full</a>
<a id="pausebacklog" class="btn#if not $backlogRunning# disabled#end if#" href="$sbRoot/manage/manageSearches/pauseBacklog?paused=#if $backlogPaused then "0" else "1"#"><i class="#if $backlogPaused then "sgicon-play" else "sgicon-pause"#"></i> #if $backlogPaused then "Unpause" else "Pause"#</a>
#if not $backlogRunning: #if not $backlogRunning:
Not in progress<br /> Not in progress<br />
#else #else
#if $backlogPaused then 'Paused: ' else ''# #if $backlogPaused then 'Paused: ' else ''#
Currently running<br /> Currently running ($backlogRunningType)<br />
#end if #end if
<br /> <br />
<h3>Recent Search:</h3> <h3>Recent Search:</h3>
<a class="btn" href="$sbRoot/manage/manageSearches/forceSearch"><i class="sgicon-play"></i> Force</a> <a id="recentsearch" class="btn#if $recentSearchStatus# disabled#end if#" href="$sbRoot/manage/manageSearches/forceSearch"><i class="sgicon-play"></i> Force</a>
#if not $recentSearchStatus #if not $recentSearchStatus
Not in progress<br /> Not in progress<br />
#else #else
@ -40,7 +40,7 @@
<br /> <br />
<h3>Find Propers Search:</h3> <h3>Find Propers Search:</h3>
<a class="btn" href="$sbRoot/manage/manageSearches/forceFindPropers"><i class="sgicon-play"></i> Force</a> <a id="propersearch" class="btn#if $findPropersStatus# disabled#end if#" href="$sbRoot/manage/manageSearches/forceFindPropers"><i class="sgicon-play"></i> Force</a>
#if not $findPropersStatus #if not $findPropersStatus
Not in progress<br /> Not in progress<br />
#else #else
@ -53,10 +53,85 @@
<br /><br /> <br /><br />
<h3>Search Queue:</h3> <h3>Search Queue:</h3>
Backlog: <i>$queueLength['backlog'] pending items</i><br /> #if $queueLength['backlog'] or $queueLength['manual'] or $queueLength['failed']
Recent: <i>$queueLength['recent'] pending items</i><br /> <input type="button" class="show-all-more btn" id="all-btn-more" value="Expand All"><input type="button" class="show-all-less btn" id="all-btn-less" value="Collapse All"></br>
Manual: <i>$queueLength['manual'] pending items</i><br /> #end if
Failed: <i>$queueLength['failed'] pending items</i><br /> </br>
Recent: <i>$queueLength['recent'] item$sickbeard.helpers.maybe_plural($queueLength['recent'])</i></br></br>
Proper: <i>$queueLength['proper'] item$sickbeard.helpers.maybe_plural($queueLength['proper'])</i></br></br>
Backlog: <i>$len($queueLength['backlog']) item$sickbeard.helpers.maybe_plural($len($queueLength['backlog']))</i>
#if $queueLength['backlog']
<input type="button" class="shows-more btn" id="backlog-btn-more" value="Expand" #if not $queueLength['backlog']# style="display:none" #end if#><input type="button" class="shows-less btn" id="backlog-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_item in $queueLength['backlog']:
#set $search_type = 'On Demand'
#if $cur_item[3]:
#if $cur_item[5]:
#set $search_type = 'Forced'
#else
#set $search_type = 'Scheduled'
#end if
#if $cur_item[4]:
#set $search_type += ' (Limited)'
#else
#set $search_type += ' (Full)'
#end if
#end if
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:80%;text-align:left;color:white">
<a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_item[0]">$cur_item[1]</a> - $sickbeard.helpers.make_search_segment_html_string($cur_item[2])
</td>
<td style="width:20%;text-align:center;color:white">$search_type</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
</br>
Manual: <i>$len($queueLength['manual']) item$sickbeard.helpers.maybe_plural($len($queueLength['manual']))</i>
#if $queueLength['manual']
<input type="button" class="shows-more btn" id="manual-btn-more" value="Expand" #if not $queueLength['manual']# style="display:none" #end if#><input type="button" class="shows-less btn" id="manual-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_item in $queueLength['manual']:
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:100%;text-align:left;color:white">
<a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_item[0]">$cur_item[1]</a> - $sickbeard.helpers.make_search_segment_html_string($cur_item[2])
</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
</br>
Failed: <i>$len($queueLength['failed']) item$sickbeard.helpers.maybe_plural($len($queueLength['failed']))</i>
#if $queueLength['failed']
<input type="button" class="shows-more btn" id="failed-btn-more" value="Expand" #if not $queueLength['failed']# style="display:none" #end if#><input type="button" class="shows-less btn" id="failed-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_item in $queueLength['failed']:
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:100%;text-align:left;color:white">
<a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_item[0]">$cur_item[1]</a> - $sickbeard.helpers.make_search_segment_html_string($cur_item[2])
</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
</div> </div>
</div> </div>

View file

@ -0,0 +1,151 @@
#import sickbeard
#from sickbeard.helpers import findCertainShow
##
#set global $title = 'Show Queue Overview'
#set global $header = 'Show Queue Overview'
#set global $sbPath = '..'
#set global $topmenu = 'manage'
##
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script type="text/javascript" src="$sbRoot/js/manageShowQueueOverview.js?$sbPID" xmlns="http://www.w3.org/1999/html"></script>
<div id="content800">
#if $varExists('header')
<h1 class="header">$header</h1>
#else
<h1 class="title">$title</h1>
#end if
<div id="summary2" class="align-left">
<h3> Daily Show Update:</h3>
<a id="showupdatebutton" class="btn#if $ShowUpdateRunning# disabled#end if#" href="$sbRoot/manage/showQueueOverview/forceShowUpdate"><i class="sgicon-play"></i> Force</a>
#if not $ShowUpdateRunning:
Not in progress<br />
#else:
Currently running<br />
#end if
</br>
<h3>Show Queue:</h3>
</br>
#if $queueLength['add'] or $queueLength['update'] or $queueLength['refresh'] or $queueLength['rename'] or $queueLength['subtitle']
<input type="button" class="show-all-more btn" id="all-btn-more" value="Expand All"><input type="button" class="show-all-less btn" id="all-btn-less" value="Collapse All"></br>
#end if
</br>
Add: <i>$len($queueLength['add']) show$sickbeard.helpers.maybe_plural($len($queueLength['add']))</i>
#if $queueLength['add']
<input type="button" class="shows-more btn" id="add-btn-more" value="Expand" #if not $queueLength['add']# style="display:none" #end if#><input type="button" class="shows-less btn" id="add-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_show in $queueLength['add']:
#set $show_name = str($cur_show[0])
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:80%;text-align:left;color:white">$show_name</td>
<td style="width:20%;text-align:center;color:white">#if $cur_show[1]#Scheduled#end if#</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
</br>
Update <span class="grey-text">(Forced / Forced Web)</span>: <i>$len($queueLength['update']) <span class="grey-text">($len($queueLength['forceupdate']) / $len($queueLength['forceupdateweb']))</span> show$sickbeard.helpers.maybe_plural($len($queueLength['update']))</i>
#if $queueLength['update']
<input type="button" class="shows-more btn" id="update-btn-more" value="Expand" #if not $queueLength['update']# style="display:none" #end if#><input type="button" class="shows-less btn" id="update-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_show in $queueLength['update']:
#set $show = $findCertainShow($showList, $cur_show[0])
#set $show_name = $show.name if $show else str($cur_show[0])
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:80%;text-align:left">
<a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_show[0]">$show_name</a>
</td>
<td style="width:20%;text-align:center;color:white">#if $cur_show[1]#Scheduled, #end if#$cur_show[2]</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
</br>
Refresh: <i>$len($queueLength['refresh']) show$sickbeard.helpers.maybe_plural($len($queueLength['refresh']))</i>
#if $queueLength['refresh']
<input type="button" class="shows-more btn" id="refresh-btn-more" value="Expand" #if not $queueLength['refresh']# style="display:none" #end if#><input type="button" class="shows-less btn" id="refresh-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_show in $queueLength['refresh']:
#set $show = $findCertainShow($showList, $cur_show[0])
#set $show_name = $show.name if $show else str($cur_show[0])
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:80%;text-align:left">
<a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_show[0]">$show_name</a>
</td>
<td style="width:20%;text-align:center;color:white">#if $cur_show[1]#Scheduled#end if#</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
</br>
Rename: <i>$len($queueLength['rename']) show$sickbeard.helpers.maybe_plural($len($queueLength['rename']))</i>
#if $queueLength['rename']
<input type="button" class="shows-more btn" id="rename-btn-more" value="Expand" #if not $queueLength['rename']# style="display:none" #end if#><input type="button" class="shows-less btn" id="rename-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_show in $queueLength['rename']:
#set $show = $findCertainShow($showList, $cur_show[0])
#set $show_name = $show.name if $show else str($cur_show[0])
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:80%;text-align:left">
<a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_show[0]">$show_name</a>
</td>
<td style="width:20%;text-align:center;color:white">#if $cur_show[1]#Scheduled#end if#</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
#if $sickbeard.USE_SUBTITLES
</br>
Subtitle: <i>$len($queueLength['subtitle']) show$sickbeard.helpers.maybe_plural($len($queueLength['subtitle']))</i>
#if $queueLength['subtitle']
<input type="button" class="shows-more btn" id="subtitle-btn-more" value="Expand" #if not $queueLength['subtitle']# style="display:none" #end if#><input type="button" class="shows-less btn" id="subtitle-btn-less" value="Collapse" style="display:none"></br>
<table class="sickbeardTable manageTable" cellspacing="1" border="0" cellpadding="0" style="display:none">
<thead></thead>
<tbody>
#set $row = 0
#for $cur_show in $queueLength['subtitle']:
#set $show = $findCertainShow($showList, $cur_show[0])
#set $show_name = $show.name if $show else str($cur_show[0])
<tr class="#echo ('odd', 'even')[$row % 2]##set $row+=1#">
<td style="width:80%;text-align:left">
<a class="whitelink" href="$sbRoot/home/displayShow?show=$cur_show[0]">$show_name</a>
</td>
<td style="width:20%;text-align:center;color:white">#if $cur_show[1]#Scheduled#end if#</td>
</tr>
#end for
</tbody>
</table>
#else
</br>
#end if
#end if
</div>
</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -0,0 +1,33 @@
$(document).ready(function() {
$('#recentsearch,#propersearch').click(function(){
$(this).addClass('disabled');
})
$('#forcebacklog,#forcefullbacklog').click(function(){
$('#forcebacklog,#forcefullbacklog').addClass('disabled');
$('#pausebacklog').removeClass('disabled');
})
$('#pausebacklog').click(function(){
$(this).addClass('disabled');
})
$('.show-all-less').click(function(){
$(this).nextAll('table').hide();
$(this).nextAll('input.shows-more').show();
$(this).nextAll('input.shows-less').hide();
})
$('.show-all-more').click(function(){
$(this).nextAll('table').show();
$(this).nextAll('input.shows-more').hide();
$(this).nextAll('input.shows-less').show();
})
$('.shows-less').click(function(){
$(this).nextAll('table:first').hide();
$(this).hide();
$(this).prevAll('input:first').show();
})
$('.shows-more').click(function(){
$(this).nextAll('table:first').show();
$(this).hide();
$(this).nextAll('input:first').show();
})
});

View file

@ -0,0 +1,26 @@
$(document).ready(function() {
$('#showupdatebutton').click(function(){
$(this).addClass('disabled');
})
$('.show-all-less').click(function(){
$(this).nextAll('table').hide();
$(this).nextAll('input.shows-more').show();
$(this).nextAll('input.shows-less').hide();
})
$('.show-all-more').click(function(){
$(this).nextAll('table').show();
$(this).nextAll('input.shows-more').hide();
$(this).nextAll('input.shows-less').show();
})
$('.shows-less').click(function(){
$(this).nextAll('table:first').hide();
$(this).hide();
$(this).prevAll('input:first').show();
})
$('.shows-more').click(function(){
$(this).nextAll('table:first').show();
$(this).hide();
$(this).nextAll('input:first').show();
})
});

View file

@ -40,12 +40,12 @@ from providers import ezrss, btn, newznab, womble, thepiratebay, torrentleech, k
freshontv, bitsoup, tokyotoshokan, animenzb, totv freshontv, bitsoup, tokyotoshokan, animenzb, totv
from sickbeard.config import CheckSection, check_setting_int, check_setting_str, check_setting_float, ConfigMigrator, \ from sickbeard.config import CheckSection, check_setting_int, check_setting_str, check_setting_float, ConfigMigrator, \
naming_ep_type, minimax naming_ep_type, minimax
from sickbeard import searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser, \ from sickbeard import searchBacklog, showUpdater, versionChecker, autoPostProcesser, \
subtitles, traktChecker subtitles, traktChecker
from sickbeard import helpers, db, exceptions, show_queue, search_queue, scheduler, show_name_helpers from sickbeard import helpers, db, exceptions, show_queue, search_queue, scheduler, show_name_helpers
from sickbeard import logger from sickbeard import logger
from sickbeard import naming from sickbeard import naming
from sickbeard import searchRecent from sickbeard import searchRecent, searchProper
from sickbeard import scene_numbering, scene_exceptions, name_cache from sickbeard import scene_numbering, scene_exceptions, name_cache
from indexers.indexer_api import indexerApi from indexers.indexer_api import indexerApi
from indexers.indexer_exceptions import indexer_shownotfound, indexer_exception, indexer_error, indexer_episodenotfound, \ from indexers.indexer_exceptions import indexer_shownotfound, indexer_exception, indexer_error, indexer_episodenotfound, \
@ -1158,34 +1158,35 @@ def initialize(consoleLogging=True):
update_now = datetime.timedelta(minutes=0) update_now = datetime.timedelta(minutes=0)
versionCheckScheduler = scheduler.Scheduler(versionChecker.CheckVersion(), versionCheckScheduler = scheduler.Scheduler(versionChecker.CheckVersion(),
cycleTime=datetime.timedelta(hours=UPDATE_FREQUENCY), cycleTime=datetime.timedelta(hours=UPDATE_FREQUENCY),
threadName="CHECKVERSION", threadName='CHECKVERSION',
silent=False) silent=False)
showQueueScheduler = scheduler.Scheduler(show_queue.ShowQueue(), showQueueScheduler = scheduler.Scheduler(show_queue.ShowQueue(),
cycleTime=datetime.timedelta(seconds=3), cycleTime=datetime.timedelta(seconds=3),
threadName="SHOWQUEUE") threadName='SHOWQUEUE')
showUpdateScheduler = scheduler.Scheduler(showUpdater.ShowUpdater(), showUpdateScheduler = scheduler.Scheduler(showUpdater.ShowUpdater(),
cycleTime=datetime.timedelta(hours=1), cycleTime=datetime.timedelta(hours=1),
threadName="SHOWUPDATER", threadName='SHOWUPDATER',
start_time=datetime.time(hour=SHOW_UPDATE_HOUR)) # 3 AM start_time=datetime.time(hour=SHOW_UPDATE_HOUR),
prevent_cycle_run=sickbeard.showQueueScheduler.action.isShowUpdateRunning) # 3 AM
# searchers # searchers
searchQueueScheduler = scheduler.Scheduler(search_queue.SearchQueue(), searchQueueScheduler = scheduler.Scheduler(search_queue.SearchQueue(),
cycleTime=datetime.timedelta(seconds=3), cycleTime=datetime.timedelta(seconds=3),
threadName="SEARCHQUEUE") threadName='SEARCHQUEUE')
update_interval = datetime.timedelta(minutes=RECENTSEARCH_FREQUENCY) update_interval = datetime.timedelta(minutes=RECENTSEARCH_FREQUENCY)
recentSearchScheduler = scheduler.Scheduler(searchRecent.RecentSearcher(), recentSearchScheduler = scheduler.Scheduler(searchRecent.RecentSearcher(),
cycleTime=update_interval, cycleTime=update_interval,
threadName="RECENTSEARCHER", threadName='RECENTSEARCHER',
run_delay=update_now if RECENTSEARCH_STARTUP run_delay=update_now if RECENTSEARCH_STARTUP
else datetime.timedelta(minutes=5), else datetime.timedelta(minutes=5),
prevent_cycle_run=sickbeard.searchQueueScheduler.action.is_recentsearch_in_progress) prevent_cycle_run=sickbeard.searchQueueScheduler.action.is_recentsearch_in_progress)
backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(), backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(),
cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()), cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()),
threadName="BACKLOG", threadName='BACKLOG',
run_delay=update_now if BACKLOG_STARTUP run_delay=update_now if BACKLOG_STARTUP
else datetime.timedelta(minutes=10), else datetime.timedelta(minutes=10),
prevent_cycle_run=sickbeard.searchQueueScheduler.action.is_standard_backlog_in_progress) prevent_cycle_run=sickbeard.searchQueueScheduler.action.is_standard_backlog_in_progress)
@ -1198,27 +1199,28 @@ def initialize(consoleLogging=True):
update_interval = datetime.timedelta(hours=1) update_interval = datetime.timedelta(hours=1)
run_at = datetime.time(hour=1) # 1 AM run_at = datetime.time(hour=1) # 1 AM
properFinderScheduler = scheduler.Scheduler(properFinder.ProperFinder(), properFinderScheduler = scheduler.Scheduler(searchProper.ProperSearcher(),
cycleTime=update_interval, cycleTime=update_interval,
threadName="FINDPROPERS", threadName='FINDPROPERS',
start_time=run_at, start_time=run_at,
run_delay=update_interval) run_delay=update_interval,
prevent_cycle_run=sickbeard.searchQueueScheduler.action.is_propersearch_in_progress)
# processors # processors
autoPostProcesserScheduler = scheduler.Scheduler(autoPostProcesser.PostProcesser(), autoPostProcesserScheduler = scheduler.Scheduler(autoPostProcesser.PostProcesser(),
cycleTime=datetime.timedelta( cycleTime=datetime.timedelta(
minutes=AUTOPOSTPROCESSER_FREQUENCY), minutes=AUTOPOSTPROCESSER_FREQUENCY),
threadName="POSTPROCESSER", threadName='POSTPROCESSER',
silent=not PROCESS_AUTOMATICALLY) silent=not PROCESS_AUTOMATICALLY)
traktCheckerScheduler = scheduler.Scheduler(traktChecker.TraktChecker(), traktCheckerScheduler = scheduler.Scheduler(traktChecker.TraktChecker(),
cycleTime=datetime.timedelta(hours=1), cycleTime=datetime.timedelta(hours=1),
threadName="TRAKTCHECKER", threadName='TRAKTCHECKER',
silent=not USE_TRAKT) silent=not USE_TRAKT)
subtitlesFinderScheduler = scheduler.Scheduler(subtitles.SubtitlesFinder(), subtitlesFinderScheduler = scheduler.Scheduler(subtitles.SubtitlesFinder(),
cycleTime=datetime.timedelta(hours=SUBTITLES_FINDER_FREQUENCY), cycleTime=datetime.timedelta(hours=SUBTITLES_FINDER_FREQUENCY),
threadName="FINDSUBTITLES", threadName='FINDSUBTITLES',
silent=not USE_SUBTITLES) silent=not USE_SUBTITLES)
showList = [] showList = []

View file

@ -35,21 +35,24 @@ class GenericQueue(object):
self.queue = [] self.queue = []
self.queue_name = "QUEUE" self.queue_name = 'QUEUE'
self.min_priority = 0 self.min_priority = 0
self.lock = threading.Lock() self.lock = threading.Lock()
def pause(self): def pause(self):
logger.log(u"Pausing queue") logger.log(u'Pausing queue')
if self.lock:
self.min_priority = 999999999999 self.min_priority = 999999999999
def unpause(self): def unpause(self):
logger.log(u"Unpausing queue") logger.log(u'Unpausing queue')
with self.lock:
self.min_priority = 0 self.min_priority = 0
def add_item(self, item): def add_item(self, item):
with self.lock:
item.added = datetime.datetime.now() item.added = datetime.datetime.now()
self.queue.append(item) self.queue.append(item)
@ -58,6 +61,7 @@ class GenericQueue(object):
def run(self, force=False): def run(self, force=False):
# only start a new task if one isn't already going # only start a new task if one isn't already going
with self.lock:
if self.currentItem is None or not self.currentItem.isAlive(): if self.currentItem is None or not self.currentItem.isAlive():
# if the thread is dead then the current item should be finished # if the thread is dead then the current item should be finished
@ -97,7 +101,7 @@ class QueueItem(threading.Thread):
def __init__(self, name, action_id=0): def __init__(self, name, action_id=0):
super(QueueItem, self).__init__() super(QueueItem, self).__init__()
self.name = name.replace(" ", "-").upper() self.name = name.replace(' ', '-').upper()
self.inProgress = False self.inProgress = False
self.priority = QueuePriorities.NORMAL self.priority = QueuePriorities.NORMAL
self.action_id = action_id self.action_id = action_id

View file

@ -1,4 +1,4 @@
# Author: Nic Wolfe <nic@wolfeden.ca> # Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/ # URL: http://code.google.com/p/sickbeard/
# #
# This file is part of SickGear. # This file is part of SickGear.
@ -1412,3 +1412,21 @@ def clear_unused_providers():
if providers: if providers:
myDB = db.DBConnection('cache.db') myDB = db.DBConnection('cache.db')
myDB.action('DELETE FROM provider_cache WHERE provider NOT IN (%s)' % ','.join(['?'] * len(providers)), providers) myDB.action('DELETE FROM provider_cache WHERE provider NOT IN (%s)' % ','.join(['?'] * len(providers)), providers)
def make_search_segment_html_string(segment, max_eps=5):
seg_str = ''
if segment and not isinstance(segment, list):
segment = [segment]
if segment and len(segment) > max_eps:
seasons = [x for x in set([x.season for x in segment])]
seg_str = u'Season' + maybe_plural(len(seasons)) + ': '
first_run = True
for x in seasons:
eps = [str(s.episode) for s in segment if s.season == x]
ep_c = len(eps)
seg_str += ('' if first_run else ' ,') + str(x) + ' <span title="Episode' + maybe_plural(ep_c) + ': ' + ', '.join(eps) + '">(' + str(ep_c) + ' Ep' + maybe_plural(ep_c) + ')</span>'
first_run = False
elif segment:
episodes = ['S' + str(x.season).zfill(2) + 'E' + str(x.episode).zfill(2) for x in segment]
seg_str = u'Episode' + maybe_plural(len(episodes)) + ': ' + ', '.join(episodes)
return seg_str

View file

@ -35,39 +35,31 @@ from sickbeard.common import DOWNLOADED, SNATCHED, SNATCHED_PROPER, Quality
from name_parser.parser import NameParser, InvalidNameException, InvalidShowException from name_parser.parser import NameParser, InvalidNameException, InvalidShowException
class ProperFinder(): def searchPropers():
def __init__(self):
self.amActive = False
def run(self):
if not sickbeard.DOWNLOAD_PROPERS: if not sickbeard.DOWNLOAD_PROPERS:
return return
logger.log(u"Beginning the search for new propers") logger.log(u'Beginning the search for new propers')
self.amActive = True propers = _getProperList()
propers = self._getProperList()
if propers: if propers:
self._downloadPropers(propers) _downloadPropers(propers)
self._set_lastProperSearch(datetime.datetime.today().toordinal()) _set_lastProperSearch(datetime.datetime.today().toordinal())
run_at = "" run_at = ''
if None is sickbeard.properFinderScheduler.start_time: if None is sickbeard.properFinderScheduler.start_time:
run_in = sickbeard.properFinderScheduler.lastRun + sickbeard.properFinderScheduler.cycleTime - datetime.datetime.now() run_in = sickbeard.properFinderScheduler.lastRun + sickbeard.properFinderScheduler.cycleTime - datetime.datetime.now()
hours, remainder = divmod(run_in.seconds, 3600) hours, remainder = divmod(run_in.seconds, 3600)
minutes, seconds = divmod(remainder, 60) minutes, seconds = divmod(remainder, 60)
run_at = u", next check in approx. " + ( run_at = u', next check in approx. ' + (
"%dh, %dm" % (hours, minutes) if 0 < hours else "%dm, %ds" % (minutes, seconds)) '%dh, %dm' % (hours, minutes) if 0 < hours else '%dm, %ds' % (minutes, seconds))
logger.log(u"Completed the search for new propers%s" % run_at) logger.log(u'Completed the search for new propers%s' % run_at)
self.amActive = False def _getProperList():
def _getProperList(self):
propers = {} propers = {}
search_date = datetime.datetime.today() - datetime.timedelta(days=2) search_date = datetime.datetime.today() - datetime.timedelta(days=2)
@ -76,17 +68,17 @@ class ProperFinder():
origThreadName = threading.currentThread().name origThreadName = threading.currentThread().name
providers = [x for x in sickbeard.providers.sortedProviderList() if x.isActive()] providers = [x for x in sickbeard.providers.sortedProviderList() if x.isActive()]
for curProvider in providers: for curProvider in providers:
threading.currentThread().name = origThreadName + " :: [" + curProvider.name + "]" threading.currentThread().name = origThreadName + ' :: [' + curProvider.name + ']'
logger.log(u"Searching for any new PROPER releases from " + curProvider.name) logger.log(u'Searching for any new PROPER releases from ' + curProvider.name)
try: try:
curPropers = curProvider.findPropers(search_date) curPropers = curProvider.findPropers(search_date)
except exceptions.AuthException, e: except exceptions.AuthException, e:
logger.log(u"Authentication error: " + ex(e), logger.ERROR) logger.log(u'Authentication error: ' + ex(e), logger.ERROR)
continue continue
except Exception, e: except Exception, e:
logger.log(u"Error while searching " + curProvider.name + ", skipping: " + ex(e), logger.ERROR) logger.log(u'Error while searching ' + curProvider.name + ', skipping: ' + ex(e), logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
continue continue
finally: finally:
@ -94,9 +86,9 @@ class ProperFinder():
# if they haven't been added by a different provider than add the proper to the list # if they haven't been added by a different provider than add the proper to the list
for x in curPropers: for x in curPropers:
name = self._genericName(x.name) name = _genericName(x.name)
if not name in propers: if not name in propers:
logger.log(u"Found new proper: " + x.name, logger.DEBUG) logger.log(u'Found new proper: ' + x.name, logger.DEBUG)
x.provider = curProvider x.provider = curProvider
propers[name] = x propers[name] = x
@ -110,10 +102,10 @@ class ProperFinder():
myParser = NameParser(False) myParser = NameParser(False)
parse_result = myParser.parse(curProper.name) parse_result = myParser.parse(curProper.name)
except InvalidNameException: except InvalidNameException:
logger.log(u"Unable to parse the filename " + curProper.name + " into a valid episode", logger.DEBUG) logger.log(u'Unable to parse the filename ' + curProper.name + ' into a valid episode', logger.DEBUG)
continue continue
except InvalidShowException: except InvalidShowException:
logger.log(u"Unable to parse the filename " + curProper.name + " into a valid show", logger.DEBUG) logger.log(u'Unable to parse the filename ' + curProper.name + ' into a valid show', logger.DEBUG)
continue continue
if not parse_result.series_name: if not parse_result.series_name:
@ -121,12 +113,12 @@ class ProperFinder():
if not parse_result.episode_numbers: if not parse_result.episode_numbers:
logger.log( logger.log(
u"Ignoring " + curProper.name + " because it's for a full season rather than specific episode", u'Ignoring ' + curProper.name + ' because it\'s for a full season rather than specific episode',
logger.DEBUG) logger.DEBUG)
continue continue
logger.log( logger.log(
u"Successful match! Result " + parse_result.original_name + " matched to show " + parse_result.show.name, u'Successful match! Result ' + parse_result.original_name + ' matched to show ' + parse_result.show.name,
logger.DEBUG) logger.DEBUG)
# set the indexerid in the db to the show's indexerid # set the indexerid in the db to the show's indexerid
@ -145,38 +137,38 @@ class ProperFinder():
# only get anime proper if it has release group and version # only get anime proper if it has release group and version
if parse_result.is_anime: if parse_result.is_anime:
if not curProper.release_group and curProper.version == -1: if not curProper.release_group and curProper.version == -1:
logger.log(u"Proper " + curProper.name + " doesn't have a release group and version, ignoring it", logger.log(u'Proper ' + curProper.name + ' doesn\'t have a release group and version, ignoring it',
logger.DEBUG) logger.DEBUG)
continue continue
if not show_name_helpers.filterBadReleases(curProper.name, parse=False): if not show_name_helpers.filterBadReleases(curProper.name, parse=False):
logger.log(u"Proper " + curProper.name + " isn't a valid scene release that we want, ignoring it", logger.log(u'Proper ' + curProper.name + ' isn\'t a valid scene release that we want, ignoring it',
logger.DEBUG) logger.DEBUG)
continue continue
if parse_result.show.rls_ignore_words and search.filter_release_name(curProper.name, if parse_result.show.rls_ignore_words and search.filter_release_name(curProper.name,
parse_result.show.rls_ignore_words): parse_result.show.rls_ignore_words):
logger.log( logger.log(
u"Ignoring " + curProper.name + " based on ignored words filter: " + parse_result.show.rls_ignore_words, u'Ignoring ' + curProper.name + ' based on ignored words filter: ' + parse_result.show.rls_ignore_words,
logger.MESSAGE) logger.MESSAGE)
continue continue
if parse_result.show.rls_require_words and not search.filter_release_name(curProper.name, if parse_result.show.rls_require_words and not search.filter_release_name(curProper.name,
parse_result.show.rls_require_words): parse_result.show.rls_require_words):
logger.log( logger.log(
u"Ignoring " + curProper.name + " based on required words filter: " + parse_result.show.rls_require_words, u'Ignoring ' + curProper.name + ' based on required words filter: ' + parse_result.show.rls_require_words,
logger.MESSAGE) logger.MESSAGE)
continue continue
# check if we actually want this proper (if it's the right quality) # check if we actually want this proper (if it's the right quality)
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select("SELECT status FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?", sqlResults = myDB.select('SELECT status FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?',
[curProper.indexerid, curProper.season, curProper.episode]) [curProper.indexerid, curProper.season, curProper.episode])
if not sqlResults: if not sqlResults:
continue continue
# only keep the proper if we have already retrieved the same quality ep (don't get better/worse ones) # only keep the proper if we have already retrieved the same quality ep (don't get better/worse ones)
oldStatus, oldQuality = Quality.splitCompositeStatus(int(sqlResults[0]["status"])) oldStatus, oldQuality = Quality.splitCompositeStatus(int(sqlResults[0]['status']))
if oldStatus not in (DOWNLOADED, SNATCHED) or oldQuality != curProper.quality: if oldStatus not in (DOWNLOADED, SNATCHED) or oldQuality != curProper.quality:
continue continue
@ -184,30 +176,30 @@ class ProperFinder():
if parse_result.is_anime: if parse_result.is_anime:
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select( sqlResults = myDB.select(
"SELECT release_group, version FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?", 'SELECT release_group, version FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?',
[curProper.indexerid, curProper.season, curProper.episode]) [curProper.indexerid, curProper.season, curProper.episode])
oldVersion = int(sqlResults[0]["version"]) oldVersion = int(sqlResults[0]['version'])
oldRelease_group = (sqlResults[0]["release_group"]) oldRelease_group = (sqlResults[0]['release_group'])
if oldVersion > -1 and oldVersion < curProper.version: if oldVersion > -1 and oldVersion < curProper.version:
logger.log("Found new anime v" + str(curProper.version) + " to replace existing v" + str(oldVersion)) logger.log('Found new anime v' + str(curProper.version) + ' to replace existing v' + str(oldVersion))
else: else:
continue continue
if oldRelease_group != curProper.release_group: if oldRelease_group != curProper.release_group:
logger.log("Skipping proper from release group: " + curProper.release_group + ", does not match existing release group: " + oldRelease_group) logger.log('Skipping proper from release group: ' + curProper.release_group + ', does not match existing release group: ' + oldRelease_group)
continue continue
# if the show is in our list and there hasn't been a proper already added for that particular episode then add it to our list of propers # if the show is in our list and there hasn't been a proper already added for that particular episode then add it to our list of propers
if curProper.indexerid != -1 and (curProper.indexerid, curProper.season, curProper.episode) not in map( if curProper.indexerid != -1 and (curProper.indexerid, curProper.season, curProper.episode) not in map(
operator.attrgetter('indexerid', 'season', 'episode'), finalPropers): operator.attrgetter('indexerid', 'season', 'episode'), finalPropers):
logger.log(u"Found a proper that we need: " + str(curProper.name)) logger.log(u'Found a proper that we need: ' + str(curProper.name))
finalPropers.append(curProper) finalPropers.append(curProper)
return finalPropers return finalPropers
def _downloadPropers(self, properList): def _downloadPropers(properList):
for curProper in properList: for curProper in properList:
@ -216,37 +208,37 @@ class ProperFinder():
# make sure the episode has been downloaded before # make sure the episode has been downloaded before
myDB = db.DBConnection() myDB = db.DBConnection()
historyResults = myDB.select( historyResults = myDB.select(
"SELECT resource FROM history " 'SELECT resource FROM history '
"WHERE showid = ? AND season = ? AND episode = ? AND quality = ? AND date >= ? " 'WHERE showid = ? AND season = ? AND episode = ? AND quality = ? AND date >= ? '
"AND action IN (" + ",".join([str(x) for x in Quality.SNATCHED]) + ")", 'AND action IN (' + ','.join([str(x) for x in Quality.SNATCHED]) + ')',
[curProper.indexerid, curProper.season, curProper.episode, curProper.quality, [curProper.indexerid, curProper.season, curProper.episode, curProper.quality,
historyLimit.strftime(history.dateFormat)]) historyLimit.strftime(history.dateFormat)])
# if we didn't download this episode in the first place we don't know what quality to use for the proper so we can't do it # if we didn't download this episode in the first place we don't know what quality to use for the proper so we can't do it
if len(historyResults) == 0: if len(historyResults) == 0:
logger.log( logger.log(
u"Unable to find an original history entry for proper " + curProper.name + " so I'm not downloading it.") u'Unable to find an original history entry for proper ' + curProper.name + ' so I\'m not downloading it.')
continue continue
else: else:
# make sure that none of the existing history downloads are the same proper we're trying to download # make sure that none of the existing history downloads are the same proper we're trying to download
clean_proper_name = self._genericName(helpers.remove_non_release_groups(curProper.name)) clean_proper_name = _genericName(helpers.remove_non_release_groups(curProper.name))
isSame = False isSame = False
for curResult in historyResults: for curResult in historyResults:
# if the result exists in history already we need to skip it # if the result exists in history already we need to skip it
if self._genericName(helpers.remove_non_release_groups(curResult["resource"])) == clean_proper_name: if _genericName(helpers.remove_non_release_groups(curResult['resource'])) == clean_proper_name:
isSame = True isSame = True
break break
if isSame: if isSame:
logger.log(u"This proper is already in history, skipping it", logger.DEBUG) logger.log(u'This proper is already in history, skipping it', logger.DEBUG)
continue continue
# get the episode object # get the episode object
showObj = helpers.findCertainShow(sickbeard.showList, curProper.indexerid) showObj = helpers.findCertainShow(sickbeard.showList, curProper.indexerid)
if showObj == None: if showObj == None:
logger.log(u"Unable to find the show with indexerid " + str( logger.log(u'Unable to find the show with indexerid ' + str(
curProper.indexerid) + " so unable to download the proper", logger.ERROR) curProper.indexerid) + ' so unable to download the proper', logger.ERROR)
continue continue
epObj = showObj.getEpisode(curProper.season, curProper.episode) epObj = showObj.getEpisode(curProper.season, curProper.episode)
@ -260,29 +252,29 @@ class ProperFinder():
# snatch it # snatch it
search.snatchEpisode(result, SNATCHED_PROPER) search.snatchEpisode(result, SNATCHED_PROPER)
def _genericName(self, name): def _genericName(name):
return name.replace(".", " ").replace("-", " ").replace("_", " ").lower() return name.replace('.', ' ').replace('-', ' ').replace('_', ' ').lower()
def _set_lastProperSearch(self, when): def _set_lastProperSearch(when):
logger.log(u"Setting the last Proper search in the DB to " + str(when), logger.DEBUG) logger.log(u'Setting the last Proper search in the DB to ' + str(when), logger.DEBUG)
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select("SELECT * FROM info") sqlResults = myDB.select('SELECT * FROM info')
if len(sqlResults) == 0: if len(sqlResults) == 0:
myDB.action("INSERT INTO info (last_backlog, last_indexer, last_proper_search) VALUES (?,?,?)", myDB.action('INSERT INTO info (last_backlog, last_indexer, last_proper_search) VALUES (?,?,?)',
[0, 0, str(when)]) [0, 0, str(when)])
else: else:
myDB.action("UPDATE info SET last_proper_search=" + str(when)) myDB.action('UPDATE info SET last_proper_search=' + str(when))
def _get_lastProperSearch(self): def _get_lastProperSearch():
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select("SELECT * FROM info") sqlResults = myDB.select('SELECT * FROM info')
try: try:
last_proper_search = datetime.date.fromordinal(int(sqlResults[0]["last_proper_search"])) last_proper_search = datetime.date.fromordinal(int(sqlResults[0]['last_proper_search']))
except: except:
return datetime.date.fromordinal(1) return datetime.date.fromordinal(1)

View file

@ -46,7 +46,6 @@ class Scheduler(threading.Thread):
def forceRun(self): def forceRun(self):
if not self.action.amActive: if not self.action.amActive:
self.lastRun = datetime.datetime.fromordinal(1)
self.force = True self.force = True
return True return True
return False return False
@ -55,6 +54,7 @@ class Scheduler(threading.Thread):
while not self.stop.is_set(): while not self.stop.is_set():
try:
current_time = datetime.datetime.now() current_time = datetime.datetime.now()
should_run = False should_run = False
@ -71,6 +71,9 @@ class Scheduler(threading.Thread):
else: else:
should_run = True should_run = True
if self.force:
should_run = True
if should_run and self.prevent_cycle_run is not None and self.prevent_cycle_run(): if should_run and self.prevent_cycle_run is not None and self.prevent_cycle_run():
logger.log(u'%s skipping this cycleTime' % self.name, logger.WARNING) logger.log(u'%s skipping this cycleTime' % self.name, logger.WARNING)
# set lastRun to only check start_time after another cycleTime # set lastRun to only check start_time after another cycleTime
@ -89,6 +92,7 @@ class Scheduler(threading.Thread):
logger.log(u"Exception generated in thread " + self.name + ": " + ex(e), logger.ERROR) logger.log(u"Exception generated in thread " + self.name + ": " + ex(e), logger.ERROR)
logger.log(repr(traceback.format_exc()), logger.DEBUG) logger.log(repr(traceback.format_exc()), logger.DEBUG)
finally:
if self.force: if self.force:
self.force = False self.force = False

View file

@ -30,11 +30,14 @@ from sickbeard import ui
from sickbeard import common from sickbeard import common
from sickbeard.search import wantedEpisodes from sickbeard.search import wantedEpisodes
NORMAL_BACKLOG = 0
LIMITED_BACKLOG = 10
FULL_BACKLOG = 20
class BacklogSearchScheduler(scheduler.Scheduler): class BacklogSearchScheduler(scheduler.Scheduler):
def forceSearch(self): def forceSearch(self, force_type=NORMAL_BACKLOG):
self.action._set_lastBacklog(1) self.force = True
self.lastRun = datetime.datetime.fromordinal(1) self.action.forcetype = force_type
def nextRun(self): def nextRun(self):
if self.action._lastBacklog <= 1: if self.action._lastBacklog <= 1:
@ -54,6 +57,7 @@ class BacklogSearcher:
self.amActive = False self.amActive = False
self.amPaused = False self.amPaused = False
self.amWaiting = False self.amWaiting = False
self.forcetype = NORMAL_BACKLOG
self._resetPI() self._resetPI()
@ -68,13 +72,13 @@ class BacklogSearcher:
return None return None
def am_running(self): def am_running(self):
logger.log(u"amWaiting: " + str(self.amWaiting) + ", amActive: " + str(self.amActive), logger.DEBUG) logger.log(u'amWaiting: ' + str(self.amWaiting) + ', amActive: ' + str(self.amActive), logger.DEBUG)
return (not self.amWaiting) and self.amActive return (not self.amWaiting) and self.amActive
def searchBacklog(self, which_shows=None): def searchBacklog(self, which_shows=None, force_type=NORMAL_BACKLOG):
if self.amActive: if self.amActive:
logger.log(u"Backlog is still running, not starting it again", logger.DEBUG) logger.log(u'Backlog is still running, not starting it again', logger.DEBUG)
return return
if which_shows: if which_shows:
@ -89,9 +93,15 @@ class BacklogSearcher:
curDate = datetime.date.today().toordinal() curDate = datetime.date.today().toordinal()
fromDate = datetime.date.fromordinal(1) fromDate = datetime.date.fromordinal(1)
if not which_shows and not curDate - self._lastBacklog >= self.cycleTime: limited_backlog = False
if (not which_shows and force_type == LIMITED_BACKLOG) or (not which_shows and force_type != FULL_BACKLOG and not curDate - self._lastBacklog >= self.cycleTime):
logger.log(u'Running limited backlog for episodes missed during the last %s day(s)' % str(sickbeard.BACKLOG_DAYS)) logger.log(u'Running limited backlog for episodes missed during the last %s day(s)' % str(sickbeard.BACKLOG_DAYS))
fromDate = datetime.date.today() - datetime.timedelta(days=sickbeard.BACKLOG_DAYS) fromDate = datetime.date.today() - datetime.timedelta(days=sickbeard.BACKLOG_DAYS)
limited_backlog = True
forced = False
if not which_shows and force_type != NORMAL_BACKLOG:
forced = True
self.amActive = True self.amActive = True
self.amPaused = False self.amPaused = False
@ -105,9 +115,9 @@ class BacklogSearcher:
segments = wantedEpisodes(curShow, fromDate, make_dict=True) segments = wantedEpisodes(curShow, fromDate, make_dict=True)
for season, segment in segments.items(): for season, segment in segments.items():
self.currentSearchInfo = {'title': curShow.name + " Season " + str(season)} self.currentSearchInfo = {'title': curShow.name + ' Season ' + str(season)}
backlog_queue_item = search_queue.BacklogQueueItem(curShow, segment, standard_backlog=standard_backlog) backlog_queue_item = search_queue.BacklogQueueItem(curShow, segment, standard_backlog=standard_backlog, limited_backlog=limited_backlog, forced=forced)
sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item) # @UndefinedVariable sickbeard.searchQueueScheduler.action.add_item(backlog_queue_item) # @UndefinedVariable
else: else:
logger.log(u'Nothing needs to be downloaded for %s, skipping' % str(curShow.name), logger.DEBUG) logger.log(u'Nothing needs to be downloaded for %s, skipping' % str(curShow.name), logger.DEBUG)
@ -122,17 +132,17 @@ class BacklogSearcher:
def _get_lastBacklog(self): def _get_lastBacklog(self):
logger.log(u"Retrieving the last check time from the DB", logger.DEBUG) logger.log(u'Retrieving the last check time from the DB', logger.DEBUG)
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select("SELECT * FROM info") sqlResults = myDB.select('SELECT * FROM info')
if len(sqlResults) == 0: if len(sqlResults) == 0:
lastBacklog = 1 lastBacklog = 1
elif sqlResults[0]["last_backlog"] == None or sqlResults[0]["last_backlog"] == "": elif sqlResults[0]['last_backlog'] == None or sqlResults[0]['last_backlog'] == '':
lastBacklog = 1 lastBacklog = 1
else: else:
lastBacklog = int(sqlResults[0]["last_backlog"]) lastBacklog = int(sqlResults[0]['last_backlog'])
if lastBacklog > datetime.date.today().toordinal(): if lastBacklog > datetime.date.today().toordinal():
lastBacklog = 1 lastBacklog = 1
@ -141,19 +151,21 @@ class BacklogSearcher:
def _set_lastBacklog(self, when): def _set_lastBacklog(self, when):
logger.log(u"Setting the last backlog in the DB to " + str(when), logger.DEBUG) logger.log(u'Setting the last backlog in the DB to ' + str(when), logger.DEBUG)
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select("SELECT * FROM info") sqlResults = myDB.select('SELECT * FROM info')
if len(sqlResults) == 0: if len(sqlResults) == 0:
myDB.action("INSERT INTO info (last_backlog, last_indexer) VALUES (?,?)", [str(when), 0]) myDB.action('INSERT INTO info (last_backlog, last_indexer) VALUES (?,?)', [str(when), 0])
else: else:
myDB.action("UPDATE info SET last_backlog=" + str(when)) myDB.action('UPDATE info SET last_backlog=' + str(when))
def run(self): def run(self):
try: try:
self.searchBacklog() force_type = self.forcetype
self.forcetype = NORMAL_BACKLOG
self.searchBacklog(force_type=force_type)
except: except:
self.amActive = False self.amActive = False
raise raise

38
sickbeard/searchProper.py Normal file
View file

@ -0,0 +1,38 @@
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of SickGear.
#
# SickGear 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.
#
# SickGear 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 SickGear. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
import threading
import sickbeard
class ProperSearcher():
def __init__(self):
self.lock = threading.Lock()
self.amActive = False
def run(self):
self.amActive = True
propersearch_queue_item = sickbeard.search_queue.ProperSearchQueueItem()
sickbeard.searchQueueScheduler.action.add_item(propersearch_queue_item)
self.amActive = False

View file

@ -25,7 +25,7 @@ import datetime
import sickbeard import sickbeard
from sickbeard import db, logger, common, exceptions, helpers, network_timezones, generic_queue, search, \ from sickbeard import db, logger, common, exceptions, helpers, network_timezones, generic_queue, search, \
failed_history, history, ui failed_history, history, ui, properFinder
from sickbeard.search import wantedEpisodes from sickbeard.search import wantedEpisodes
@ -35,6 +35,7 @@ BACKLOG_SEARCH = 10
RECENT_SEARCH = 20 RECENT_SEARCH = 20
FAILED_SEARCH = 30 FAILED_SEARCH = 30
MANUAL_SEARCH = 40 MANUAL_SEARCH = 40
PROPER_SEARCH = 50
MANUAL_SEARCH_HISTORY = [] MANUAL_SEARCH_HISTORY = []
MANUAL_SEARCH_HISTORY_SIZE = 100 MANUAL_SEARCH_HISTORY_SIZE = 100
@ -42,27 +43,31 @@ MANUAL_SEARCH_HISTORY_SIZE = 100
class SearchQueue(generic_queue.GenericQueue): class SearchQueue(generic_queue.GenericQueue):
def __init__(self): def __init__(self):
generic_queue.GenericQueue.__init__(self) generic_queue.GenericQueue.__init__(self)
self.queue_name = "SEARCHQUEUE" self.queue_name = 'SEARCHQUEUE'
def is_in_queue(self, show, segment): def is_in_queue(self, show, segment):
with self.lock:
for cur_item in self.queue: for cur_item in self.queue:
if isinstance(cur_item, BacklogQueueItem) and cur_item.show == show and cur_item.segment == segment: if isinstance(cur_item, BacklogQueueItem) and cur_item.show == show and cur_item.segment == segment:
return True return True
return False return False
def is_ep_in_queue(self, segment): def is_ep_in_queue(self, segment):
with self.lock:
for cur_item in self.queue: for cur_item in self.queue:
if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and cur_item.segment == segment: if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and cur_item.segment == segment:
return True return True
return False return False
def is_show_in_queue(self, show): def is_show_in_queue(self, show):
with self.lock:
for cur_item in self.queue: for cur_item in self.queue:
if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and cur_item.show.indexerid == show: if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and cur_item.show.indexerid == show:
return True return True
return False return False
def get_all_ep_from_queue(self, show): def get_all_ep_from_queue(self, show):
with self.lock:
ep_obj_list = [] ep_obj_list = []
for cur_item in self.queue: for cur_item in self.queue:
if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and str(cur_item.show.indexerid) == show: if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and str(cur_item.show.indexerid) == show:
@ -73,56 +78,87 @@ class SearchQueue(generic_queue.GenericQueue):
return False return False
def pause_backlog(self): def pause_backlog(self):
with self.lock:
self.min_priority = generic_queue.QueuePriorities.HIGH self.min_priority = generic_queue.QueuePriorities.HIGH
def unpause_backlog(self): def unpause_backlog(self):
with self.lock:
self.min_priority = 0 self.min_priority = 0
def is_backlog_paused(self): def is_backlog_paused(self):
# backlog priorities are NORMAL, this should be done properly somewhere # backlog priorities are NORMAL, this should be done properly somewhere
with self.lock:
return self.min_priority >= generic_queue.QueuePriorities.NORMAL return self.min_priority >= generic_queue.QueuePriorities.NORMAL
def _is_in_progress(self, itemType):
with self.lock:
for cur_item in self.queue + [self.currentItem]:
if isinstance(cur_item, itemType):
return True
return False
def is_manualsearch_in_progress(self): def is_manualsearch_in_progress(self):
# Only referenced in webserve.py, only current running manualsearch or failedsearch is needed!! # Only referenced in webserve.py, only current running manualsearch or failedsearch is needed!!
if isinstance(self.currentItem, (ManualSearchQueueItem, FailedQueueItem)): return self._is_in_progress((ManualSearchQueueItem, FailedQueueItem))
return True
return False
def is_backlog_in_progress(self): def is_backlog_in_progress(self):
for cur_item in self.queue + [self.currentItem]: return self._is_in_progress(BacklogQueueItem)
if isinstance(cur_item, BacklogQueueItem):
return True def is_recentsearch_in_progress(self):
return False return self._is_in_progress(RecentSearchQueueItem)
def is_propersearch_in_progress(self):
return self._is_in_progress(ProperSearchQueueItem)
def is_standard_backlog_in_progress(self): def is_standard_backlog_in_progress(self):
with self.lock:
for cur_item in self.queue + [self.currentItem]: for cur_item in self.queue + [self.currentItem]:
if isinstance(cur_item, BacklogQueueItem) and cur_item.standard_backlog: if isinstance(cur_item, BacklogQueueItem) and cur_item.standard_backlog:
return True return True
return False return False
def is_recentsearch_in_progress(self): def type_of_backlog_in_progress(self):
limited = full = other = False
with self.lock:
for cur_item in self.queue + [self.currentItem]: for cur_item in self.queue + [self.currentItem]:
if isinstance(cur_item, RecentSearchQueueItem): if isinstance(cur_item, BacklogQueueItem):
return True if cur_item.standard_backlog:
return False if cur_item.limited_backlog:
limited = True
else:
full = True
else:
other = True
types = []
for msg, variant in ['Limited', limited], ['Full', full], ['On Demand', other]:
if variant:
types.append(msg)
message = 'None'
if types:
message = ', '.join(types)
return message
def queue_length(self): def queue_length(self):
length = {'backlog': 0, 'recent': 0, 'manual': 0, 'failed': 0} length = {'backlog': [], 'recent': 0, 'manual': [], 'failed': [], 'proper': 0}
for cur_item in self.queue: with self.lock:
for cur_item in [self.currentItem] + self.queue:
if isinstance(cur_item, RecentSearchQueueItem): if isinstance(cur_item, RecentSearchQueueItem):
length['recent'] += 1 length['recent'] += 1
elif isinstance(cur_item, BacklogQueueItem): elif isinstance(cur_item, BacklogQueueItem):
length['backlog'] += 1 length['backlog'].append([cur_item.show.indexerid, cur_item.show.name, cur_item.segment, cur_item.standard_backlog, cur_item.limited_backlog, cur_item.forced])
elif isinstance(cur_item, ProperSearchQueueItem):
length['proper'] += 1
elif isinstance(cur_item, ManualSearchQueueItem): elif isinstance(cur_item, ManualSearchQueueItem):
length['manual'] += 1 length['manual'].append([cur_item.show.indexerid, cur_item.show.name, cur_item.segment])
elif isinstance(cur_item, FailedQueueItem): elif isinstance(cur_item, FailedQueueItem):
length['failed'] += 1 length['failed'].append([cur_item.show.indexerid, cur_item.show.name, cur_item.segment])
return length return length
def add_item(self, item): def add_item(self, item):
if isinstance(item, RecentSearchQueueItem): if isinstance(item, (RecentSearchQueueItem, ProperSearchQueueItem)):
# recent searches # recent and proper searches
generic_queue.GenericQueue.add_item(self, item) generic_queue.GenericQueue.add_item(self, item)
elif isinstance(item, BacklogQueueItem) and not self.is_in_queue(item.show, item.segment): elif isinstance(item, BacklogQueueItem) and not self.is_in_queue(item.show, item.segment):
# backlog searches # backlog searches
@ -131,7 +167,7 @@ class SearchQueue(generic_queue.GenericQueue):
# manual and failed searches # manual and failed searches
generic_queue.GenericQueue.add_item(self, item) generic_queue.GenericQueue.add_item(self, item)
else: else:
logger.log(u"Not adding item, it's already in the queue", logger.DEBUG) logger.log(u'Not adding item, it\'s already in the queue', logger.DEBUG)
class RecentSearchQueueItem(generic_queue.QueueItem): class RecentSearchQueueItem(generic_queue.QueueItem):
@ -143,6 +179,7 @@ class RecentSearchQueueItem(generic_queue.QueueItem):
def run(self): def run(self):
generic_queue.QueueItem.run(self) generic_queue.QueueItem.run(self)
try:
self._change_missing_episodes() self._change_missing_episodes()
self.update_providers() self.update_providers()
@ -185,6 +222,7 @@ class RecentSearchQueueItem(generic_queue.QueueItem):
if self.success is None: if self.success is None:
self.success = False self.success = False
finally:
self.finish() self.finish()
@staticmethod @staticmethod
@ -268,6 +306,21 @@ class RecentSearchQueueItem(generic_queue.QueueItem):
logger.log('Finished updating provider caches') logger.log('Finished updating provider caches')
class ProperSearchQueueItem(generic_queue.QueueItem):
def __init__(self):
generic_queue.QueueItem.__init__(self, 'Proper Search', PROPER_SEARCH)
self.priority = generic_queue.QueuePriorities.HIGH
self.success = None
def run(self):
generic_queue.QueueItem.run(self)
try:
properFinder.searchPropers()
finally:
self.finish()
class ManualSearchQueueItem(generic_queue.QueueItem): class ManualSearchQueueItem(generic_queue.QueueItem):
def __init__(self, show, segment): def __init__(self, show, segment):
generic_queue.QueueItem.__init__(self, 'Manual Search', MANUAL_SEARCH) generic_queue.QueueItem.__init__(self, 'Manual Search', MANUAL_SEARCH)
@ -282,14 +335,14 @@ class ManualSearchQueueItem(generic_queue.QueueItem):
generic_queue.QueueItem.run(self) generic_queue.QueueItem.run(self)
try: try:
logger.log("Beginning manual search for: [" + self.segment.prettyName() + "]") logger.log('Beginning manual search for: [' + self.segment.prettyName() + ']')
self.started = True self.started = True
searchResult = search.searchProviders(self.show, [self.segment], True) searchResult = search.searchProviders(self.show, [self.segment], True)
if searchResult: if searchResult:
# just use the first result for now # just use the first result for now
logger.log(u"Downloading " + searchResult[0].name + " from " + searchResult[0].provider.name) logger.log(u'Downloading ' + searchResult[0].name + ' from ' + searchResult[0].provider.name)
self.success = search.snatchEpisode(searchResult[0]) self.success = search.snatchEpisode(searchResult[0])
# give the CPU a break # give the CPU a break
@ -297,13 +350,14 @@ class ManualSearchQueueItem(generic_queue.QueueItem):
else: else:
ui.notifications.message('No downloads were found', ui.notifications.message('No downloads were found',
"Couldn't find a download for <i>%s</i>" % self.segment.prettyName()) 'Couldn\'t find a download for <i>%s</i>' % self.segment.prettyName())
logger.log(u"Unable to find a download for: [" + self.segment.prettyName() + "]") logger.log(u'Unable to find a download for: [' + self.segment.prettyName() + ']')
except Exception: except Exception:
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
finally:
### Keep a list with the 100 last executed searches ### Keep a list with the 100 last executed searches
fifo(MANUAL_SEARCH_HISTORY, self, MANUAL_SEARCH_HISTORY_SIZE) fifo(MANUAL_SEARCH_HISTORY, self, MANUAL_SEARCH_HISTORY_SIZE)
@ -314,7 +368,7 @@ class ManualSearchQueueItem(generic_queue.QueueItem):
class BacklogQueueItem(generic_queue.QueueItem): class BacklogQueueItem(generic_queue.QueueItem):
def __init__(self, show, segment, standard_backlog=False): def __init__(self, show, segment, standard_backlog=False, limited_backlog=False, forced=False):
generic_queue.QueueItem.__init__(self, 'Backlog', BACKLOG_SEARCH) generic_queue.QueueItem.__init__(self, 'Backlog', BACKLOG_SEARCH)
self.priority = generic_queue.QueuePriorities.LOW self.priority = generic_queue.QueuePriorities.LOW
self.name = 'BACKLOG-' + str(show.indexerid) self.name = 'BACKLOG-' + str(show.indexerid)
@ -322,27 +376,30 @@ class BacklogQueueItem(generic_queue.QueueItem):
self.show = show self.show = show
self.segment = segment self.segment = segment
self.standard_backlog = standard_backlog self.standard_backlog = standard_backlog
self.limited_backlog = limited_backlog
self.forced = forced
def run(self): def run(self):
generic_queue.QueueItem.run(self) generic_queue.QueueItem.run(self)
try: try:
logger.log("Beginning backlog search for: [" + self.show.name + "]") logger.log('Beginning backlog search for: [' + self.show.name + ']')
searchResult = search.searchProviders(self.show, self.segment, False) searchResult = search.searchProviders(self.show, self.segment, False)
if searchResult: if searchResult:
for result in searchResult: for result in searchResult:
# just use the first result for now # just use the first result for now
logger.log(u"Downloading " + result.name + " from " + result.provider.name) logger.log(u'Downloading ' + result.name + ' from ' + result.provider.name)
search.snatchEpisode(result) search.snatchEpisode(result)
# give the CPU a break # give the CPU a break
time.sleep(common.cpu_presets[sickbeard.CPU_PRESET]) time.sleep(common.cpu_presets[sickbeard.CPU_PRESET])
else: else:
logger.log(u"No needed episodes found during backlog search for: [" + self.show.name + "]") logger.log(u'No needed episodes found during backlog search for: [' + self.show.name + ']')
except Exception: except Exception:
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
finally:
self.finish() self.finish()
@ -363,7 +420,7 @@ class FailedQueueItem(generic_queue.QueueItem):
try: try:
for epObj in self.segment: for epObj in self.segment:
logger.log(u"Marking episode as bad: [" + epObj.prettyName() + "]") logger.log(u'Marking episode as bad: [' + epObj.prettyName() + ']')
failed_history.markFailed(epObj) failed_history.markFailed(epObj)
@ -373,14 +430,14 @@ class FailedQueueItem(generic_queue.QueueItem):
history.logFailed(epObj, release, provider) history.logFailed(epObj, release, provider)
failed_history.revertEpisode(epObj) failed_history.revertEpisode(epObj)
logger.log("Beginning failed download search for: [" + epObj.prettyName() + "]") logger.log('Beginning failed download search for: [' + epObj.prettyName() + ']')
searchResult = search.searchProviders(self.show, self.segment, True) searchResult = search.searchProviders(self.show, self.segment, True)
if searchResult: if searchResult:
for result in searchResult: for result in searchResult:
# just use the first result for now # just use the first result for now
logger.log(u"Downloading " + result.name + " from " + result.provider.name) logger.log(u'Downloading ' + result.name + ' from ' + result.provider.name)
search.snatchEpisode(result) search.snatchEpisode(result)
# give the CPU a break # give the CPU a break
@ -391,6 +448,7 @@ class FailedQueueItem(generic_queue.QueueItem):
except Exception: except Exception:
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
finally:
### Keep a list with the 100 last executed searches ### Keep a list with the 100 last executed searches
fifo(MANUAL_SEARCH_HISTORY, self, MANUAL_SEARCH_HISTORY_SIZE) fifo(MANUAL_SEARCH_HISTORY, self, MANUAL_SEARCH_HISTORY_SIZE)

View file

@ -31,9 +31,14 @@ from sickbeard import network_timezones
from sickbeard import failed_history from sickbeard import failed_history
class ShowUpdater(): class ShowUpdater():
def __init__(self):
self.amActive = False
def run(self, force=False): def run(self, force=False):
self.amActive = True
try:
update_datetime = datetime.datetime.now() update_datetime = datetime.datetime.now()
update_date = update_datetime.date() update_date = update_datetime.date()
@ -47,21 +52,23 @@ class ShowUpdater():
# clear the data of unused providers # clear the data of unused providers
sickbeard.helpers.clear_unused_providers() sickbeard.helpers.clear_unused_providers()
logger.log(u"Doing full update on all shows") logger.log(u'Doing full update on all shows')
# clean out cache directory, remove everything > 12 hours old # clean out cache directory, remove everything > 12 hours old
sickbeard.helpers.clearCache() sickbeard.helpers.clearCache()
# select 10 'Ended' tv_shows updated more than 90 days ago to include in this update # select 10 'Ended' tv_shows updated more than 90 days ago and all shows not updated more then 180 days ago to include in this update
stale_should_update = [] stale_should_update = []
stale_update_date = (update_date - datetime.timedelta(days=90)).toordinal() stale_update_date = (update_date - datetime.timedelta(days=90)).toordinal()
stale_update_date_max = (update_date - datetime.timedelta(days=180)).toordinal()
# last_update_date <= 90 days, sorted ASC because dates are ordinal # last_update_date <= 90 days, sorted ASC because dates are ordinal
myDB = db.DBConnection() myDB = db.DBConnection()
sql_result = myDB.select( sql_results = myDB.mass_action([[
"SELECT indexer_id FROM tv_shows WHERE status = 'Ended' AND last_update_indexer <= ? ORDER BY last_update_indexer ASC LIMIT 10;", 'SELECT indexer_id FROM tv_shows WHERE last_update_indexer <= ? AND last_update_indexer >= ? ORDER BY last_update_indexer ASC LIMIT 10;',
[stale_update_date]) [stale_update_date, stale_update_date_max]], ['SELECT indexer_id FROM tv_shows WHERE last_update_indexer < ?;', [stale_update_date_max]]])
for sql_result in sql_results:
for cur_result in sql_result: for cur_result in sql_result:
stale_should_update.append(int(cur_result['indexer_id'])) stale_should_update.append(int(cur_result['indexer_id']))
@ -75,21 +82,24 @@ class ShowUpdater():
# if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update, otherwise just refresh # 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: if curShow.should_update(update_date=update_date) or curShow.indexerid in stale_should_update:
curQueueItem = sickbeard.showQueueScheduler.action.updateShow(curShow, True) # @UndefinedVariable curQueueItem = sickbeard.showQueueScheduler.action.updateShow(curShow, scheduled_update=True) # @UndefinedVariable
else: else:
logger.log( 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.", 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) logger.DEBUG)
curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(curShow, True) # @UndefinedVariable curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(curShow, True, True) # @UndefinedVariable
piList.append(curQueueItem) piList.append(curQueueItem)
except (exceptions.CantUpdateException, exceptions.CantRefreshException), e: except (exceptions.CantUpdateException, exceptions.CantRefreshException), e:
logger.log(u"Automatic update failed: " + ex(e), logger.ERROR) logger.log(u'Automatic update failed: ' + ex(e), logger.ERROR)
ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator("Daily Update", piList)) ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', piList))
logger.log(u"Completed full update on all shows") logger.log(u'Added all shows to show queue for full update')
finally:
self.amActive = False
def __del__(self): def __del__(self):
pass pass

View file

@ -145,7 +145,7 @@ def makeSceneSeasonSearchString(show, ep_obj, extraSearchType=None):
# for providers that don't allow multiple searches in one request we only search for Sxx style stuff # for providers that don't allow multiple searches in one request we only search for Sxx style stuff
else: else:
for cur_season in seasonStrings: for cur_season in seasonStrings:
if len(show.release_groups.whitelist) > 0: if show.is_anime and show.release_groups is not None and show.release_groups.whitelist:
for keyword in show.release_groups.whitelist: for keyword in show.release_groups.whitelist:
toReturn.append(keyword + '.' + curShow+ "." + cur_season) toReturn.append(keyword + '.' + curShow+ "." + cur_season)
else: else:
@ -182,7 +182,7 @@ def makeSceneSearchString(show, ep_obj):
for curShow in showNames: for curShow in showNames:
for curEpString in epStrings: for curEpString in epStrings:
if len(ep_obj.show.release_groups.whitelist) > 0: if ep_obj.show.is_anime and ep_obj.show.release_groups is not None and ep_obj.show.release_groups.whitelist:
for keyword in ep_obj.show.release_groups.whitelist: for keyword in ep_obj.show.release_groups.whitelist:
toReturn.append(keyword + '.' + curShow + '.' + curEpString) toReturn.append(keyword + '.' + curShow + '.' + curEpString)
else: else:

View file

@ -1,4 +1,4 @@
# Author: Nic Wolfe <nic@wolfeden.ca> # Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/ # URL: http://code.google.com/p/sickbeard/
# #
# This file is part of SickGear. # This file is part of SickGear.
@ -34,12 +34,14 @@ from sickbeard.blackandwhitelist import BlackAndWhiteList
class ShowQueue(generic_queue.GenericQueue): class ShowQueue(generic_queue.GenericQueue):
def __init__(self): def __init__(self):
generic_queue.GenericQueue.__init__(self) generic_queue.GenericQueue.__init__(self)
self.queue_name = "SHOWQUEUE" self.queue_name = 'SHOWQUEUE'
def _isInQueue(self, show, actions): def _isInQueue(self, show, actions):
with self.lock:
return show in [x.show for x in self.queue if x.action_id in actions] return show in [x.show for x in self.queue if x.action_id in actions]
def _isBeingSomethinged(self, show, actions): def _isBeingSomethinged(self, show, actions):
with self.lock:
return self.currentItem != None and show == self.currentItem.show and \ return self.currentItem != None and show == self.currentItem.show and \
self.currentItem.action_id in actions self.currentItem.action_id in actions
@ -70,48 +72,77 @@ class ShowQueue(generic_queue.GenericQueue):
def isBeingSubtitled(self, show): def isBeingSubtitled(self, show):
return self._isBeingSomethinged(show, (ShowQueueActions.SUBTITLE,)) return self._isBeingSomethinged(show, (ShowQueueActions.SUBTITLE,))
def isShowUpdateRunning(self):
with self.lock:
for x in self.queue + [self.currentItem]:
if isinstance(x, ShowQueueItem) and x.scheduled_update:
return True
return False
def _getLoadingShowList(self): def _getLoadingShowList(self):
with self.lock:
return [x for x in self.queue + [self.currentItem] if x != None and x.isLoading] return [x for x in self.queue + [self.currentItem] if x != None and x.isLoading]
def queue_length(self):
length = {'add': [], 'update': [], 'forceupdate': [], 'forceupdateweb': [], 'refresh': [], 'rename': [], 'subtitle': []}
with self.lock:
for cur_item in [self.currentItem] + self.queue:
if isinstance(cur_item, QueueItemAdd):
length['add'].append([cur_item.show_name, cur_item.scheduled_update])
elif isinstance(cur_item, QueueItemUpdate):
update_type = 'Normal'
if isinstance(cur_item, QueueItemForceUpdate):
update_type = 'Forced'
elif isinstance(cur_item, QueueItemForceUpdateWeb):
update_type = 'Forced Web'
length['update'].append([cur_item.show_name, cur_item.scheduled_update, update_type])
elif isinstance(cur_item, QueueItemRefresh):
length['refresh'].append([cur_item.show_name, cur_item.scheduled_update])
elif isinstance(cur_item, QueueItemRename):
length['rename'].append([cur_item.show_name, cur_item.scheduled_update])
elif isinstance(cur_item, QueueItemSubtitle):
length['subtitle'].append([cur_item.show_name, cur_item.scheduled_update])
return length
loadingShowList = property(_getLoadingShowList) loadingShowList = property(_getLoadingShowList)
def updateShow(self, show, force=False, web=False): def updateShow(self, show, force=False, web=False, scheduled_update=False):
if self.isBeingAdded(show): if self.isBeingAdded(show):
raise exceptions.CantUpdateException( raise exceptions.CantUpdateException(
"Show is still being added, wait until it is finished before you update.") 'Show is still being added, wait until it is finished before you update.')
if self.isBeingUpdated(show): if self.isBeingUpdated(show):
raise exceptions.CantUpdateException( raise exceptions.CantUpdateException(
"This show is already being updated, can't update again until it's done.") 'This show is already being updated, can\'t update again until it\'s done.')
if self.isInUpdateQueue(show): if self.isInUpdateQueue(show):
raise exceptions.CantUpdateException( raise exceptions.CantUpdateException(
"This show is already being updated, can't update again until it's done.") 'This show is already being updated, can\'t update again until it\'s done.')
if not force: if not force:
queueItemObj = QueueItemUpdate(show) queueItemObj = QueueItemUpdate(show, scheduled_update=scheduled_update)
elif web: elif web:
queueItemObj = QueueItemForceUpdateWeb(show) queueItemObj = QueueItemForceUpdateWeb(show, scheduled_update=scheduled_update)
else: else:
queueItemObj = QueueItemForceUpdate(show) queueItemObj = QueueItemForceUpdate(show, scheduled_update=scheduled_update)
self.add_item(queueItemObj) self.add_item(queueItemObj)
return queueItemObj return queueItemObj
def refreshShow(self, show, force=False): def refreshShow(self, show, force=False, scheduled_update=False):
if self.isBeingRefreshed(show) and not force: if self.isBeingRefreshed(show) and not force:
raise exceptions.CantRefreshException("This show is already being refreshed, not refreshing again.") raise exceptions.CantRefreshException('This show is already being refreshed, not refreshing again.')
if (self.isBeingUpdated(show) or self.isInUpdateQueue(show)) and not force: if (self.isBeingUpdated(show) or self.isInUpdateQueue(show)) and not force:
logger.log( logger.log(
u"A refresh was attempted but there is already an update queued or in progress. Since updates do a refresh at the end anyway I'm skipping this request.", u'A refresh was attempted but there is already an update queued or in progress. Since updates do a refresh at the end anyway I\'m skipping this request.',
logger.DEBUG) logger.DEBUG)
return return
queueItemObj = QueueItemRefresh(show, force=force) queueItemObj = QueueItemRefresh(show, force=force, scheduled_update=scheduled_update)
self.add_item(queueItemObj) self.add_item(queueItemObj)
@ -134,7 +165,7 @@ class ShowQueue(generic_queue.GenericQueue):
return queueItemObj return queueItemObj
def addShow(self, indexer, indexer_id, showDir, default_status=None, quality=None, flatten_folders=None, def addShow(self, indexer, indexer_id, showDir, default_status=None, quality=None, flatten_folders=None,
lang="en", subtitles=None, anime=None, scene=None, paused=None, blacklist=None, whitelist=None, lang='en', subtitles=None, anime=None, scene=None, paused=None, blacklist=None, whitelist=None,
wanted_begin=None, wanted_latest=None, tag=None): wanted_begin=None, wanted_latest=None, tag=None):
queueItemObj = QueueItemAdd(indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, queueItemObj = QueueItemAdd(indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang,
subtitles, anime, scene, paused, blacklist, whitelist, subtitles, anime, scene, paused, blacklist, whitelist,
@ -173,9 +204,10 @@ class ShowQueueItem(generic_queue.QueueItem):
- show being subtitled - show being subtitled
""" """
def __init__(self, action_id, show): def __init__(self, action_id, show, scheduled_update=False):
generic_queue.QueueItem.__init__(self, ShowQueueActions.names[action_id], action_id) generic_queue.QueueItem.__init__(self, ShowQueueActions.names[action_id], action_id)
self.show = show self.show = show
self.scheduled_update = scheduled_update
def isInQueue(self): def isInQueue(self):
return self in sickbeard.showQueueScheduler.action.queue + [ return self in sickbeard.showQueueScheduler.action.queue + [
@ -194,7 +226,7 @@ class ShowQueueItem(generic_queue.QueueItem):
class QueueItemAdd(ShowQueueItem): class QueueItemAdd(ShowQueueItem):
def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles, anime, def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles, anime,
scene, paused, blacklist, whitelist, default_wanted_begin, default_wanted_latest, tag): scene, paused, blacklist, whitelist, default_wanted_begin, default_wanted_latest, tag, scheduled_update=False):
self.indexer = indexer self.indexer = indexer
self.indexer_id = indexer_id self.indexer_id = indexer_id
@ -216,7 +248,7 @@ class QueueItemAdd(ShowQueueItem):
self.show = None self.show = None
# this will initialize self.show to None # this will initialize self.show to None
ShowQueueItem.__init__(self, ShowQueueActions.ADD, self.show) ShowQueueItem.__init__(self, ShowQueueActions.ADD, self.show, scheduled_update)
def _getName(self): def _getName(self):
""" """
@ -244,7 +276,7 @@ class QueueItemAdd(ShowQueueItem):
ShowQueueItem.run(self) ShowQueueItem.run(self)
logger.log(u"Starting to add show " + self.showDir) logger.log(u'Starting to add show ' + self.showDir)
# make sure the Indexer IDs are valid # make sure the Indexer IDs are valid
try: try:
@ -252,37 +284,37 @@ class QueueItemAdd(ShowQueueItem):
if self.lang: if self.lang:
lINDEXER_API_PARMS['language'] = self.lang lINDEXER_API_PARMS['language'] = self.lang
logger.log(u"" + str(sickbeard.indexerApi(self.indexer).name) + ": " + repr(lINDEXER_API_PARMS)) logger.log(u'' + str(sickbeard.indexerApi(self.indexer).name) + ': ' + repr(lINDEXER_API_PARMS))
t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS) t = sickbeard.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS)
s = t[self.indexer_id] s = t[self.indexer_id]
# this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that has no proper english version of the show # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that has no proper english version of the show
if getattr(s, 'seriesname', None) is None: if getattr(s, 'seriesname', None) is None:
logger.log(u"Show in " + self.showDir + " has no name on " + str( logger.log(u'Show in ' + self.showDir + ' has no name on ' + str(
sickbeard.indexerApi(self.indexer).name) + ", probably the wrong language used to search with.", sickbeard.indexerApi(self.indexer).name) + ', probably the wrong language used to search with.',
logger.ERROR) logger.ERROR)
ui.notifications.error("Unable to add show", ui.notifications.error('Unable to add show',
"Show in " + self.showDir + " has no name on " + str(sickbeard.indexerApi( 'Show in ' + self.showDir + ' has no name on ' + str(sickbeard.indexerApi(
self.indexer).name) + ", probably the wrong language. Delete .nfo and add manually in the correct language.") self.indexer).name) + ', probably the wrong language. Delete .nfo and add manually in the correct language.')
self._finishEarly() self._finishEarly()
return return
# if the show has no episodes/seasons # if the show has no episodes/seasons
if not s: if not s:
logger.log(u"Show " + str(s['seriesname']) + " is on " + str( logger.log(u'Show ' + str(s['seriesname']) + ' is on ' + str(
sickbeard.indexerApi(self.indexer).name) + " but contains no season/episode data.", logger.ERROR) sickbeard.indexerApi(self.indexer).name) + ' but contains no season/episode data.', logger.ERROR)
ui.notifications.error("Unable to add show", ui.notifications.error('Unable to add show',
"Show " + str(s['seriesname']) + " is on " + str(sickbeard.indexerApi( 'Show ' + str(s['seriesname']) + ' is on ' + str(sickbeard.indexerApi(
self.indexer).name) + " but contains no season/episode data.") self.indexer).name) + ' but contains no season/episode data.')
self._finishEarly() self._finishEarly()
return return
except Exception, e: except Exception, e:
logger.log(u"Unable to find show ID:" + str(self.indexer_id) + " on Indexer: " + str( logger.log(u'Unable to find show ID:' + str(self.indexer_id) + ' on Indexer: ' + str(
sickbeard.indexerApi(self.indexer).name), logger.ERROR) sickbeard.indexerApi(self.indexer).name), logger.ERROR)
ui.notifications.error("Unable to add show", ui.notifications.error('Unable to add show',
"Unable to look up the show in " + self.showDir + " on " + str(sickbeard.indexerApi( 'Unable to look up the show in ' + self.showDir + ' on ' + str(sickbeard.indexerApi(
self.indexer).name) + " using ID " + str( self.indexer).name) + ' using ID ' + str(
self.indexer_id) + ", not using the NFO. Delete .nfo and try adding manually again.") self.indexer_id) + ', not using the NFO. Delete .nfo and try adding manually again.')
self._finishEarly() self._finishEarly()
return return
@ -310,35 +342,35 @@ class QueueItemAdd(ShowQueueItem):
self.show.release_groups.set_white_keywords(self.whitelist) self.show.release_groups.set_white_keywords(self.whitelist)
# be smartish about this # be smartish about this
if self.show.genre and "talk show" in self.show.genre.lower(): if self.show.genre and 'talk show' in self.show.genre.lower():
self.show.air_by_date = 1 self.show.air_by_date = 1
if self.show.genre and "documentary" in self.show.genre.lower(): if self.show.genre and 'documentary' in self.show.genre.lower():
self.show.air_by_date = 0 self.show.air_by_date = 0
if self.show.classification and "sports" in self.show.classification.lower(): if self.show.classification and 'sports' in self.show.classification.lower():
self.show.sports = 1 self.show.sports = 1
except sickbeard.indexer_exception, e: except sickbeard.indexer_exception, e:
logger.log( logger.log(
u"Unable to add show due to an error with " + sickbeard.indexerApi(self.indexer).name + ": " + ex(e), u'Unable to add show due to an error with ' + sickbeard.indexerApi(self.indexer).name + ': ' + ex(e),
logger.ERROR) logger.ERROR)
if self.show: if self.show:
ui.notifications.error( ui.notifications.error(
"Unable to add " + str(self.show.name) + " due to an error with " + sickbeard.indexerApi( 'Unable to add ' + str(self.show.name) + ' due to an error with ' + sickbeard.indexerApi(
self.indexer).name + "") self.indexer).name + '')
else: else:
ui.notifications.error( ui.notifications.error(
"Unable to add show due to an error with " + sickbeard.indexerApi(self.indexer).name + "") 'Unable to add show due to an error with ' + sickbeard.indexerApi(self.indexer).name + '')
self._finishEarly() self._finishEarly()
return return
except exceptions.MultipleShowObjectsException: except exceptions.MultipleShowObjectsException:
logger.log(u"The show in " + self.showDir + " is already in your show list, skipping", logger.ERROR) logger.log(u'The show in ' + self.showDir + ' is already in your show list, skipping', logger.ERROR)
ui.notifications.error('Show skipped', "The show in " + self.showDir + " is already in your show list") ui.notifications.error('Show skipped', 'The show in ' + self.showDir + ' is already in your show list')
self._finishEarly() self._finishEarly()
return return
except Exception, e: except Exception, e:
logger.log(u"Error trying to add show: " + ex(e), logger.ERROR) logger.log(u'Error trying to add show: ' + ex(e), logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
self._finishEarly() self._finishEarly()
raise raise
@ -348,7 +380,7 @@ class QueueItemAdd(ShowQueueItem):
try: try:
self.show.saveToDB() self.show.saveToDB()
except Exception, e: except Exception, e:
logger.log(u"Error saving the show to the database: " + ex(e), logger.ERROR) logger.log(u'Error saving the show to the database: ' + ex(e), logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
self._finishEarly() self._finishEarly()
raise raise
@ -360,7 +392,7 @@ class QueueItemAdd(ShowQueueItem):
self.show.loadEpisodesFromIndexer() self.show.loadEpisodesFromIndexer()
except Exception, e: except Exception, e:
logger.log( logger.log(
u"Error with " + sickbeard.indexerApi(self.show.indexer).name + ", not creating episode list: " + ex(e), u'Error with ' + sickbeard.indexerApi(self.show.indexer).name + ', not creating episode list: ' + ex(e),
logger.ERROR) logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
@ -370,14 +402,14 @@ class QueueItemAdd(ShowQueueItem):
try: try:
self.show.loadEpisodesFromDir() self.show.loadEpisodesFromDir()
except Exception, e: except Exception, e:
logger.log(u"Error searching directory for episodes: " + ex(e), logger.ERROR) logger.log(u'Error searching directory for episodes: ' + ex(e), logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
# if they gave a custom status then change all the eps to it # if they gave a custom status then change all the eps to it
my_db = db.DBConnection() my_db = db.DBConnection()
if self.default_status != SKIPPED: if self.default_status != SKIPPED:
logger.log(u"Setting all episodes to the specified default status: " + str(self.default_status)) logger.log(u'Setting all episodes to the specified default status: ' + str(self.default_status))
my_db.action("UPDATE tv_episodes SET status = ? WHERE status = ? AND showid = ? AND season != 0", my_db.action('UPDATE tv_episodes SET status = ? WHERE status = ? AND showid = ? AND season != 0',
[self.default_status, SKIPPED, self.show.indexerid]) [self.default_status, SKIPPED, self.show.indexerid])
# if they gave a number to start or number to end as wanted, then change those eps to it # if they gave a number to start or number to end as wanted, then change those eps to it
@ -399,7 +431,7 @@ class QueueItemAdd(ShowQueueItem):
wanted_updates = db_obj.select(select) wanted_updates = db_obj.select(select)
db_obj.action(update) db_obj.action(update)
result = db_obj.select("SELECT changes() as last FROM [tv_episodes]") result = db_obj.select('SELECT changes() as last FROM [tv_episodes]')
for cur_result in result: for cur_result in result:
actual = cur_result['last'] actual = cur_result['last']
break break
@ -457,8 +489,8 @@ class QueueItemAdd(ShowQueueItem):
class QueueItemRefresh(ShowQueueItem): class QueueItemRefresh(ShowQueueItem):
def __init__(self, show=None, force=False): def __init__(self, show=None, force=False, scheduled_update=False):
ShowQueueItem.__init__(self, ShowQueueActions.REFRESH, show) ShowQueueItem.__init__(self, ShowQueueActions.REFRESH, show, scheduled_update)
# do refreshes first because they're quick # do refreshes first because they're quick
self.priority = generic_queue.QueuePriorities.HIGH self.priority = generic_queue.QueuePriorities.HIGH
@ -469,7 +501,7 @@ class QueueItemRefresh(ShowQueueItem):
def run(self): def run(self):
ShowQueueItem.run(self) ShowQueueItem.run(self)
logger.log(u"Performing refresh on " + self.show.name) logger.log(u'Performing refresh on ' + self.show.name)
self.show.refreshDir() self.show.refreshDir()
self.show.writeMetadata() self.show.writeMetadata()
@ -484,19 +516,19 @@ class QueueItemRefresh(ShowQueueItem):
class QueueItemRename(ShowQueueItem): class QueueItemRename(ShowQueueItem):
def __init__(self, show=None): def __init__(self, show=None, scheduled_update=False):
ShowQueueItem.__init__(self, ShowQueueActions.RENAME, show) ShowQueueItem.__init__(self, ShowQueueActions.RENAME, show, scheduled_update)
def run(self): def run(self):
ShowQueueItem.run(self) ShowQueueItem.run(self)
logger.log(u"Performing rename on " + self.show.name) logger.log(u'Performing rename on ' + self.show.name)
try: try:
show_loc = self.show.location show_loc = self.show.location
except exceptions.ShowDirNotFoundException: except exceptions.ShowDirNotFoundException:
logger.log(u"Can't perform rename on " + self.show.name + " when the show directory is missing.", logger.WARNING) logger.log(u'Can\'t perform rename on ' + self.show.name + ' when the show directory is missing.', logger.WARNING)
return return
ep_obj_rename_list = [] ep_obj_rename_list = []
@ -525,13 +557,13 @@ class QueueItemRename(ShowQueueItem):
class QueueItemSubtitle(ShowQueueItem): class QueueItemSubtitle(ShowQueueItem):
def __init__(self, show=None): def __init__(self, show=None, scheduled_update=False):
ShowQueueItem.__init__(self, ShowQueueActions.SUBTITLE, show) ShowQueueItem.__init__(self, ShowQueueActions.SUBTITLE, show, scheduled_update)
def run(self): def run(self):
ShowQueueItem.run(self) ShowQueueItem.run(self)
logger.log(u"Downloading subtitles for " + self.show.name) logger.log(u'Downloading subtitles for ' + self.show.name)
self.show.downloadSubtitles() self.show.downloadSubtitles()
@ -539,8 +571,8 @@ class QueueItemSubtitle(ShowQueueItem):
class QueueItemUpdate(ShowQueueItem): class QueueItemUpdate(ShowQueueItem):
def __init__(self, show=None): def __init__(self, show=None, scheduled_update=False):
ShowQueueItem.__init__(self, ShowQueueActions.UPDATE, show) ShowQueueItem.__init__(self, ShowQueueActions.UPDATE, show, scheduled_update)
self.force = False self.force = False
self.force_web = False self.force_web = False
@ -548,20 +580,20 @@ class QueueItemUpdate(ShowQueueItem):
ShowQueueItem.run(self) ShowQueueItem.run(self)
logger.log(u"Beginning update of " + self.show.name) logger.log(u'Beginning update of ' + self.show.name)
logger.log(u"Retrieving show info from " + sickbeard.indexerApi(self.show.indexer).name + "", logger.DEBUG) logger.log(u'Retrieving show info from ' + sickbeard.indexerApi(self.show.indexer).name + '', logger.DEBUG)
try: try:
result = self.show.loadFromIndexer(cache=not self.force) result = self.show.loadFromIndexer(cache=not self.force)
if None is not result: if None is not result:
return return
except sickbeard.indexer_error, e: except sickbeard.indexer_error, e:
logger.log(u"Unable to contact " + sickbeard.indexerApi(self.show.indexer).name + ", aborting: " + ex(e), logger.log(u'Unable to contact ' + sickbeard.indexerApi(self.show.indexer).name + ', aborting: ' + ex(e),
logger.WARNING) logger.WARNING)
return return
except sickbeard.indexer_attributenotfound, e: except sickbeard.indexer_attributenotfound, e:
logger.log(u"Data retrieved from " + sickbeard.indexerApi( logger.log(u'Data retrieved from ' + sickbeard.indexerApi(
self.show.indexer).name + " was incomplete, aborting: " + ex(e), logger.ERROR) self.show.indexer).name + ' was incomplete, aborting: ' + ex(e), logger.ERROR)
return return
if self.force_web: if self.force_web:
@ -570,30 +602,30 @@ class QueueItemUpdate(ShowQueueItem):
try: try:
self.show.saveToDB() self.show.saveToDB()
except Exception, e: except Exception, e:
logger.log(u"Error saving the episode to the database: " + ex(e), logger.ERROR) logger.log(u'Error saving the episode to the database: ' + ex(e), logger.ERROR)
logger.log(traceback.format_exc(), logger.DEBUG) logger.log(traceback.format_exc(), logger.DEBUG)
# get episode list from DB # get episode list from DB
logger.log(u"Loading all episodes from the database", logger.DEBUG) logger.log(u'Loading all episodes from the database', logger.DEBUG)
DBEpList = self.show.loadEpisodesFromDB() DBEpList = self.show.loadEpisodesFromDB()
# get episode list from TVDB # get episode list from TVDB
logger.log(u"Loading all episodes from " + sickbeard.indexerApi(self.show.indexer).name + "", logger.DEBUG) logger.log(u'Loading all episodes from ' + sickbeard.indexerApi(self.show.indexer).name + '', logger.DEBUG)
try: try:
IndexerEpList = self.show.loadEpisodesFromIndexer(cache=not self.force) IndexerEpList = self.show.loadEpisodesFromIndexer(cache=not self.force)
except sickbeard.indexer_exception, e: except sickbeard.indexer_exception, e:
logger.log(u"Unable to get info from " + sickbeard.indexerApi( logger.log(u'Unable to get info from ' + sickbeard.indexerApi(
self.show.indexer).name + ", the show info will not be refreshed: " + ex(e), logger.ERROR) self.show.indexer).name + ', the show info will not be refreshed: ' + ex(e), logger.ERROR)
IndexerEpList = None IndexerEpList = None
if IndexerEpList == None: if IndexerEpList == None:
logger.log(u"No data returned from " + sickbeard.indexerApi( logger.log(u'No data returned from ' + sickbeard.indexerApi(
self.show.indexer).name + ", unable to update this show", logger.ERROR) self.show.indexer).name + ', unable to update this show', logger.ERROR)
else: else:
# for each ep we found on TVDB delete it from the DB list # for each ep we found on TVDB delete it from the DB list
for curSeason in IndexerEpList: for curSeason in IndexerEpList:
for curEpisode in IndexerEpList[curSeason]: for curEpisode in IndexerEpList[curSeason]:
logger.log(u"Removing " + str(curSeason) + "x" + str(curEpisode) + " from the DB list", logger.log(u'Removing ' + str(curSeason) + 'x' + str(curEpisode) + ' from the DB list',
logger.DEBUG) logger.DEBUG)
if curSeason in DBEpList and curEpisode in DBEpList[curSeason]: if curSeason in DBEpList and curEpisode in DBEpList[curSeason]:
del DBEpList[curSeason][curEpisode] del DBEpList[curSeason][curEpisode]
@ -601,8 +633,8 @@ class QueueItemUpdate(ShowQueueItem):
# for the remaining episodes in the DB list just delete them from the DB # for the remaining episodes in the DB list just delete them from the DB
for curSeason in DBEpList: for curSeason in DBEpList:
for curEpisode in DBEpList[curSeason]: for curEpisode in DBEpList[curSeason]:
logger.log(u"Permanently deleting episode " + str(curSeason) + "x" + str( logger.log(u'Permanently deleting episode ' + str(curSeason) + 'x' + str(
curEpisode) + " from the database", logger.MESSAGE) curEpisode) + ' from the database', logger.MESSAGE)
curEp = self.show.getEpisode(curSeason, curEpisode) curEp = self.show.getEpisode(curSeason, curEpisode)
try: try:
curEp.deleteEpisode() curEp.deleteEpisode()
@ -613,14 +645,14 @@ class QueueItemUpdate(ShowQueueItem):
class QueueItemForceUpdate(QueueItemUpdate): class QueueItemForceUpdate(QueueItemUpdate):
def __init__(self, show=None): def __init__(self, show=None, scheduled_update=False):
ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show) ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show, scheduled_update)
self.force = True self.force = True
self.force_web = False self.force_web = False
class QueueItemForceUpdateWeb(QueueItemUpdate): class QueueItemForceUpdateWeb(QueueItemUpdate):
def __init__(self, show=None): def __init__(self, show=None, scheduled_update=False):
ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show) ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show, scheduled_update)
self.force = True self.force = True
self.force_web = True self.force_web = True

View file

@ -289,47 +289,39 @@ class TVShow(object):
# In some situations self.status = None.. need to figure out where that is! # In some situations self.status = None.. need to figure out where that is!
if not self.status: if not self.status:
self.status = '' self.status = ''
logger.log("Status missing for showid: [%s] with status: [%s]" % logger.log('Status missing for showid: [%s] with status: [%s]' %
(cur_indexerid, self.status), logger.DEBUG) (cur_indexerid, self.status), logger.DEBUG)
# if show is not 'Ended' always update (status 'Continuing' or '')
if 'Ended' not in self.status:
return True
# run logic against the current show latest aired and next unaired data to see if we should bypass 'Ended' status
graceperiod = datetime.timedelta(days=30)
last_airdate = datetime.date.fromordinal(1)
# get latest aired episode to compare against today - graceperiod and today + graceperiod
myDB = db.DBConnection() myDB = db.DBConnection()
sql_result = myDB.select( sql_result = myDB.mass_action(
"SELECT * FROM tv_episodes WHERE showid = ? AND season > '0' AND airdate > '1' AND status > '1' ORDER BY airdate DESC LIMIT 1", [['SELECT airdate FROM [tv_episodes] WHERE showid = ? AND season > "0" ORDER BY season DESC, episode DESC LIMIT 1', [cur_indexerid]],
[cur_indexerid]) ['SELECT airdate FROM [tv_episodes] WHERE showid = ? AND season > "0" AND airdate > "1" ORDER BY airdate DESC LIMIT 1', [cur_indexerid]]])
if sql_result: last_airdate_unknown = int(sql_result[0][0]['airdate']) <= 1 if sql_result and sql_result[0] else True
last_airdate = datetime.date.fromordinal(sql_result[0]['airdate'])
if last_airdate >= (update_date - graceperiod) and last_airdate <= (update_date + graceperiod):
return True
# get next upcoming UNAIRED episode to compare against today + graceperiod last_airdate = datetime.date.fromordinal(sql_result[1][0]['airdate']) if sql_result and sql_result[1] else datetime.date.fromordinal(1)
sql_result = myDB.select(
"SELECT * FROM tv_episodes WHERE showid = ? AND season > '0' AND airdate > '1' AND status = '1' ORDER BY airdate ASC LIMIT 1",
[cur_indexerid])
if sql_result:
next_airdate = datetime.date.fromordinal(sql_result[0]['airdate'])
if next_airdate <= (update_date + graceperiod):
return True
last_update_indexer = datetime.date.fromordinal(self.last_update_indexer) last_update_indexer = datetime.date.fromordinal(self.last_update_indexer)
# in the first year after ended (last airdate), update every 30 days # if show is not 'Ended' and last episode aired less then 460 days ago or don't have an airdate for the last episode always update (status 'Continuing' or '')
if (update_date - last_airdate) < datetime.timedelta(days=450) and ( update_days_limit = 460
update_date - last_update_indexer) > datetime.timedelta(days=30): ended_limit = datetime.timedelta(days=update_days_limit)
if 'Ended' not in self.status and (last_airdate == datetime.date.fromordinal(1) or last_airdate_unknown or (update_date - last_airdate) <= ended_limit or (update_date - last_update_indexer) > ended_limit):
return True return True
# in the first 460 days (last airdate), update regularly
airdate_diff = update_date - last_airdate
last_update_diff = update_date - last_update_indexer
update_step_list = [[60, 1], [120, 3], [180, 7], [365, 15], [update_days_limit, 30]]
for date_diff, interval in update_step_list:
if airdate_diff <= datetime.timedelta(days=date_diff) and last_update_diff >= datetime.timedelta(days=interval):
return True
# update shows without an airdate for the last episode for 460 days every 7 days
if last_airdate_unknown and airdate_diff <= ended_limit and last_update_diff >= datetime.timedelta(days=7):
return True
else:
return False return False
def writeShowNFO(self): def writeShowNFO(self):
@ -1009,22 +1001,22 @@ class TVShow(object):
logger.log(str(self.indexerid) + u': Parsed latest IMDb show info for [%s]' % self.name) logger.log(str(self.indexerid) + u': Parsed latest IMDb show info for [%s]' % self.name)
def nextEpisode(self): def nextEpisode(self):
logger.log(str(self.indexerid) + ": Finding the episode which airs next", logger.DEBUG) logger.log(str(self.indexerid) + ': Finding the episode which airs next', logger.DEBUG)
curDate = datetime.date.today().toordinal() curDate = datetime.date.today().toordinal()
if not self.nextaired or self.nextaired and curDate > self.nextaired: if not self.nextaired or self.nextaired and curDate > self.nextaired:
myDB = db.DBConnection() myDB = db.DBConnection()
sqlResults = myDB.select( sqlResults = myDB.select(
"SELECT airdate, season, episode FROM tv_episodes WHERE showid = ? AND airdate >= ? AND status in (?,?) ORDER BY airdate ASC LIMIT 1", 'SELECT airdate, season, episode FROM tv_episodes WHERE showid = ? AND airdate >= ? AND status in (?,?,?) ORDER BY airdate ASC LIMIT 1',
[self.indexerid, datetime.date.today().toordinal(), UNAIRED, WANTED]) [self.indexerid, datetime.date.today().toordinal(), UNAIRED, WANTED, FAILED])
if sqlResults == None or len(sqlResults) == 0: if sqlResults == None or len(sqlResults) == 0:
logger.log(str(self.indexerid) + u": No episode found... need to implement a show status", logger.log(str(self.indexerid) + u': No episode found... need to implement a show status',
logger.DEBUG) logger.DEBUG)
self.nextaired = "" self.nextaired = ''
else: else:
logger.log(str(self.indexerid) + u": Found episode " + str(sqlResults[0]["season"]) + "x" + str( logger.log(str(self.indexerid) + u': Found episode ' + str(sqlResults[0]['season']) + 'x' + str(
sqlResults[0]["episode"]), logger.DEBUG) sqlResults[0]['episode']), logger.DEBUG)
self.nextaired = sqlResults[0]['airdate'] self.nextaired = sqlResults[0]['airdate']
return self.nextaired return self.nextaired

View file

@ -46,6 +46,7 @@ from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering,
from sickbeard.name_cache import buildNameCache from sickbeard.name_cache import buildNameCache
from sickbeard.browser import foldersAtPath from sickbeard.browser import foldersAtPath
from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names
from sickbeard.searchBacklog import FULL_BACKLOG, LIMITED_BACKLOG
from tornado import gen from tornado import gen
from tornado.web import RequestHandler, authenticated from tornado.web import RequestHandler, authenticated
from lib import adba from lib import adba
@ -2516,6 +2517,7 @@ class Manage(MainHandler):
manageMenu = [ manageMenu = [
{'title': 'Backlog Overview', 'path': 'manage/backlogOverview/'}, {'title': 'Backlog Overview', 'path': 'manage/backlogOverview/'},
{'title': 'Manage Searches', 'path': 'manage/manageSearches/'}, {'title': 'Manage Searches', 'path': 'manage/manageSearches/'},
{'title': 'Show Queue Overview', 'path': 'manage/showQueueOverview/'},
{'title': 'Episode Status Management', 'path': 'manage/episodeStatuses/'}, ] {'title': 'Episode Status Management', 'path': 'manage/episodeStatuses/'}, ]
if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \ if sickbeard.USE_TORRENTS and sickbeard.TORRENT_METHOD != 'blackhole' \
@ -3223,8 +3225,9 @@ class ManageSearches(Manage):
# t.backlogPI = sickbeard.backlogSearchScheduler.action.getProgressIndicator() # t.backlogPI = sickbeard.backlogSearchScheduler.action.getProgressIndicator()
t.backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused() t.backlogPaused = sickbeard.searchQueueScheduler.action.is_backlog_paused()
t.backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress() t.backlogRunning = sickbeard.searchQueueScheduler.action.is_backlog_in_progress()
t.backlogRunningType = sickbeard.searchQueueScheduler.action.type_of_backlog_in_progress()
t.recentSearchStatus = sickbeard.searchQueueScheduler.action.is_recentsearch_in_progress() t.recentSearchStatus = sickbeard.searchQueueScheduler.action.is_recentsearch_in_progress()
t.findPropersStatus = sickbeard.properFinderScheduler.action.amActive t.findPropersStatus = sickbeard.searchQueueScheduler.action.is_propersearch_in_progress()
t.queueLength = sickbeard.searchQueueScheduler.action.queue_length() t.queueLength = sickbeard.searchQueueScheduler.action.queue_length()
t.submenu = self.ManageMenu() t.submenu = self.ManageMenu()
@ -3238,23 +3241,36 @@ class ManageSearches(Manage):
self.redirect('/home/') self.redirect('/home/')
def forceBacklog(self, *args, **kwargs): def forceLimitedBacklog(self, *args, **kwargs):
# force it to run the next time it looks # force it to run the next time it looks
result = sickbeard.backlogSearchScheduler.forceRun() if not sickbeard.searchQueueScheduler.action.is_standard_backlog_in_progress():
if result: sickbeard.backlogSearchScheduler.forceSearch(force_type=LIMITED_BACKLOG)
logger.log(u'Backlog search forced') logger.log(u'Limited Backlog search forced')
ui.notifications.message('Backlog search started') ui.notifications.message('Limited Backlog search started')
time.sleep(5)
self.redirect('/manage/manageSearches/')
def forceFullBacklog(self, *args, **kwargs):
# force it to run the next time it looks
if not sickbeard.searchQueueScheduler.action.is_standard_backlog_in_progress():
sickbeard.backlogSearchScheduler.forceSearch(force_type=FULL_BACKLOG)
logger.log(u'Full Backlog search forced')
ui.notifications.message('Full Backlog search started')
time.sleep(5)
self.redirect('/manage/manageSearches/') self.redirect('/manage/manageSearches/')
def forceSearch(self, *args, **kwargs): def forceSearch(self, *args, **kwargs):
# force it to run the next time it looks # force it to run the next time it looks
if not sickbeard.searchQueueScheduler.action.is_recentsearch_in_progress():
result = sickbeard.recentSearchScheduler.forceRun() result = sickbeard.recentSearchScheduler.forceRun()
if result: if result:
logger.log(u'Recent search forced') logger.log(u'Recent search forced')
ui.notifications.message('Recent search started') ui.notifications.message('Recent search started')
time.sleep(5)
self.redirect('/manage/manageSearches/') self.redirect('/manage/manageSearches/')
def forceFindPropers(self, *args, **kwargs): def forceFindPropers(self, *args, **kwargs):
@ -3265,6 +3281,7 @@ class ManageSearches(Manage):
logger.log(u'Find propers search forced') logger.log(u'Find propers search forced')
ui.notifications.message('Find propers search started') ui.notifications.message('Find propers search started')
time.sleep(5)
self.redirect('/manage/manageSearches/') self.redirect('/manage/manageSearches/')
def pauseBacklog(self, paused=None): def pauseBacklog(self, paused=None):
@ -3273,8 +3290,29 @@ class ManageSearches(Manage):
else: else:
sickbeard.searchQueueScheduler.action.unpause_backlog() # @UndefinedVariable sickbeard.searchQueueScheduler.action.unpause_backlog() # @UndefinedVariable
time.sleep(5)
self.redirect('/manage/manageSearches/') self.redirect('/manage/manageSearches/')
class showQueueOverview(Manage):
def index(self, *args, **kwargs):
t = PageTemplate(headers=self.request.headers, file='manage_showQueueOverview.tmpl')
t.queueLength = sickbeard.showQueueScheduler.action.queue_length()
t.showList = sickbeard.showList
t.ShowUpdateRunning = sickbeard.showQueueScheduler.action.isShowUpdateRunning()
t.submenu = self.ManageMenu()
return t.respond()
def forceShowUpdate(self, *args, **kwargs):
result = sickbeard.showUpdateScheduler.forceRun()
if result:
logger.log(u'Show Update forced')
ui.notifications.message('Forced Show Update started')
time.sleep(5)
self.redirect('/manage/showQueueOverview/')
class History(MainHandler): class History(MainHandler):
def index(self, limit=100): def index(self, limit=100):

View file

@ -83,6 +83,7 @@ class WebServer(threading.Thread):
(r'%s/home/postprocess(/?.*)' % self.options['web_root'], webserve.HomePostProcess), (r'%s/home/postprocess(/?.*)' % self.options['web_root'], webserve.HomePostProcess),
(r'%s/home(/?.*)' % self.options['web_root'], webserve.Home), (r'%s/home(/?.*)' % self.options['web_root'], webserve.Home),
(r'%s/manage/manageSearches(/?.*)' % self.options['web_root'], webserve.ManageSearches), (r'%s/manage/manageSearches(/?.*)' % self.options['web_root'], webserve.ManageSearches),
(r'%s/manage/showQueueOverview(/?.*)' % self.options['web_root'], webserve.showQueueOverview),
(r'%s/manage/(/?.*)' % self.options['web_root'], webserve.Manage), (r'%s/manage/(/?.*)' % self.options['web_root'], webserve.Manage),
(r'%s/ui(/?.*)' % self.options['web_root'], webserve.UI), (r'%s/ui(/?.*)' % self.options['web_root'], webserve.UI),
(r'%s/browser(/?.*)' % self.options['web_root'], webserve.WebFileBrowser), (r'%s/browser(/?.*)' % self.options['web_root'], webserve.WebFileBrowser),