Change refactor manual search processing.

Change reduce browser I/O on displayShow.
Fix displayShow bug where click holding on a season btn and then dragging away leaves 50% white.

Devel:
Change sets and lists are assigned by reference, so snatched_eps are deep copied in base_info().
Change comment out BaseSearchQueueItem::copy() as deprecated for base_info().
Change improve ajax consumer to reduce polling.
Simplify SimpleNamespace init in base_info().
Use base info instead of thread object for MANUAL_SEARCH_HISTORY in (ManualSearchQueueItem + FailedQueueItem) to streamline the finished search processing in webserve, this means add_dt has to be moved to BaseSearchQueueItem for base_info().
SimpleNameSpace Ref error is now in PYC, not sure if this is valid tho.
This commit is contained in:
JackDandy 2018-10-02 20:03:01 +01:00
parent 89905fc94d
commit ac6d1f69d9
10 changed files with 319 additions and 276 deletions

View file

@ -24,6 +24,10 @@
* Add search results sort by oldest aired * Add search results sort by oldest aired
* Change requirements.txt Cheetah >= 3.1.0 * Change requirements.txt Cheetah >= 3.1.0
* Add Snowfl torrent provider * Add Snowfl torrent provider
* Fix manual search button on displayShow and episode view page
* Change feedback result of manual search on the clicked button image/tooltip
* Change reduce browser I/O on displayShow
* Fix displayShow bug where click holding on a season btn and then dragging away leaves 50% white
[develop changelog] [develop changelog]

View file

@ -1060,6 +1060,7 @@ fieldset[disabled] .navbar-default .btn-link:focus{
outline:thin dotted #333; outline:thin dotted #333;
outline:5px auto -webkit-focus-ring-color; outline:5px auto -webkit-focus-ring-color;
outline-offset:-2px; outline-offset:-2px;
background-position:0;
color:#ddd color:#ddd
} }

View file

@ -2130,6 +2130,19 @@ td.col-search{
width:46px width:46px
} }
td.col-search{
font-size:10px
}
.ep-search,
.ep-retry,
.ep-search img[src=""],
.ep-retry img[src=""]{
display:inline-block;
width:16px;
height:16px
}
#testRenameTable tbody td.col-checkbox, #testRenameTable tbody td.col-checkbox,
#testRenameTable tbody td.col-ep{width:1%;vertical-align:middle} #testRenameTable tbody td.col-ep{width:1%;vertical-align:middle}
#testRenameTable tbody td.col-name{ #testRenameTable tbody td.col-name{

View file

@ -50,11 +50,11 @@
<script type="text/javascript" src="$sbRoot/js/lib/jquery.collapser.min.js?v=$sbPID"></script> <script type="text/javascript" src="$sbRoot/js/lib/jquery.collapser.min.js?v=$sbPID"></script>
<style> <style>
.bfr{position:absolute;left:-999px;top:-999px}.bfr img{width:16px;height:16px}.spinner{display:inline-block;width:16px;height:16px;background:url(${sbRoot}/images/loading16${theme_suffix}.gif) no-repeat 0 0} .bfr{position:absolute;left:-999px;top:-999px}.bfr img,img.spinner,img.queued,img.search{display:inline-block;width:16px;height:16px}img.spinner{background:url(${sbRoot}/images/loading16${theme_suffix}.gif) no-repeat 0 0}img.queued{background:url(${sbRoot}/images/queued.png) no-repeat 0 0}img.search{background:url(${sbRoot}/images/search16.png) no-repeat 0 0}
.images i{margin-right:6px;margin-top:5px}.hide{display:none} .images i{margin-right:6px;margin-top:5px}.hide{display:none}
.tvshowImg {border:1px solid transparent;min-width:226px;min-hieght:332px} .tvshowImg {border:1px solid transparent;min-width:226px;min-hieght:332px}
</style> </style>
<div class="bfr"><img src="$sbRoot/images/loading16${theme_suffix}.gif" /></div> <div class="bfr"><img src="$sbRoot/images/loading16${theme_suffix}.gif"><img src="$sbRoot/images/queued.png"><img src="$sbRoot/images/search16.png"><img src="$sbRoot/images/no16.png"><img src="$sbRoot/images/yes16.png"></div>
<div id="background-container"> <div id="background-container">
#if $has_art #if $has_art

View file

@ -17,6 +17,7 @@
#set $restart = 'Restart SickGear for new features on this page' #set $restart = 'Restart SickGear for new features on this page'
#set $show_message = (None, $restart)[not $varExists('fanart')] #set $show_message = (None, $restart)[not $varExists('fanart')]
#set global $page_body_attr = 'episode-view" class="' + $css #set global $page_body_attr = 'episode-view" class="' + $css
#set theme_suffix = ('', '-dark')['dark' == $sg_str('THEME_NAME', 'dark')]
## ##
#import os.path #import os.path
#include $os.path.join($sg_str('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl') #include $os.path.join($sg_str('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl')
@ -176,7 +177,9 @@
.asc{border-top:0; border-bottom:8px solid} .asc{border-top:0; border-bottom:8px solid}
.desc{border-top:8px solid; border-bottom:0} .desc{border-top:8px solid; border-bottom:0}
#end if #end if
.bfr{position:absolute;left:-999px;top:-999px}.bfr img,img.spinner,img.queued,img.search{display:inline-block;width:16px;height:16px}img.spinner{background:url(${sbRoot}/images/loading16${theme_suffix}.gif) no-repeat 0 0}img.queued{background:url(${sbRoot}/images/queued.png) no-repeat 0 0}img.search{background:url(${sbRoot}/images/search16.png) no-repeat 0 0}
</style> </style>
<div class="bfr"><img src="$sbRoot/images/loading16${theme_suffix}.gif"><img src="$sbRoot/images/queued.png"><img src="$sbRoot/images/search16.png"><img src="$sbRoot/images/no16.png"><img src="$sbRoot/images/yes16.png"></div>
#if $show_message #if $show_message
<div class="alert alert-info" style="margin:-40px 0 50px"> <div class="alert alert-info" style="margin:-40px 0 50px">
@ -293,9 +296,9 @@
}); });
$(document).ready(function(){ $(document).ready(function(){
#end raw
sortList = [[$table_sort_header_codes[$sort], 0]]; var sortList = [[$table_sort_header_codes[$sort], 0]];
#raw
$('#showListTable:has(tbody tr)').tablesorter({ $('#showListTable:has(tbody tr)').tablesorter({
widgets: ['stickyHeaders'], widgets: ['stickyHeaders'],
sortList: sortList, sortList: sortList,
@ -381,9 +384,10 @@
#end if #end if
#end if #end if
#set $show_id = '%s_%sx%s' % (str($cur_result['showid']), str($cur_result['season']), str($cur_result['episode'])) #set $show_id = '%s_%sx%s' % ($cur_result['showid'], $cur_result['season'], $cur_result['episode'])
#set $id_sxe = '%s_%s' % ($cur_result['indexer'], $show_id)
<!-- start $cur_result['show_name'] //--> <!-- start $cur_result['show_name'] //-->
<tr id="show-${show_id}" class="$show_div" data-rawname="$cur_result['show_name']"> <tr id="show-${show_id}" class="$show_div" data-rawname="$cur_result['show_name']" data-show-id="$id_sxe">
## forced to use a div to wrap airdate, the column sort went crazy with a span ## forced to use a div to wrap airdate, the column sort went crazy with a span
<td align="center" class="nowrap"> <td align="center" class="nowrap">
<div class="${fuzzydate}">$sbdatetime.sbdatetime.sbfdatetime($cur_result['localtime']).decode($sickbeard.SYS_ENCODING)</div><span class="sort-data">$cur_result['localtime'].strftime('%Y%m%d%H%M')</span> <div class="${fuzzydate}">$sbdatetime.sbdatetime.sbfdatetime($cur_result['localtime']).decode($sickbeard.SYS_ENCODING)</div><span class="sort-data">$cur_result['localtime'].strftime('%Y%m%d%H%M')</span>
@ -428,7 +432,7 @@
</td> </td>
<td align="center"> <td align="center">
<a href="$sbRoot/home/searchEpisode?show=${cur_result['showid']}&amp;season=$cur_result['season']&amp;episode=$cur_result['episode']" title="Manual Search" id="forceUpdate-${cur_result['showid']}" class="forceUpdate epSearch"><img alt="[search]" height="16" width="16" src="$sbRoot/images/search16.png" id="forceUpdateImage-${cur_result['showid']}" /></a> <a class="ep-search" href="$sbRoot/home/episode_search?show=${cur_result['showid']}&amp;season=$cur_result['season']&amp;episode=$cur_result['episode']" title="Manual Search"><img title="[search]" alt="[search]" height="16" width="16" src="$sbRoot/images/search16.png" /></a>
</td> </td>
</tr> </tr>
<!-- end $cur_result['show_name'] //--> <!-- end $cur_result['show_name'] //-->
@ -455,7 +459,7 @@
<!-- <!--
#raw #raw
$(document).ready(function(){ $(document).ready(function(){
$('#sbRoot').ajaxEpSearch({'size': 16, 'loadingImage': 'loading16' + themeSpinner + '.gif'}); $('#sbRoot').ajaxEpSearch();
$('.ep_summary').hide(); $('.ep_summary').hide();
$('.ep_summaryTrigger').click(function() { $('.ep_summaryTrigger').click(function() {
$(this).next('.ep_summary').slideToggle('normal', function() { $(this).next('.ep_summary').slideToggle('normal', function() {
@ -588,7 +592,8 @@
#end if #end if
#slurp #slurp
<!-- start $cur_result['show_name'] //--> <!-- start $cur_result['show_name'] //-->
<div class="$show_div" id="listing-${cur_result['showid']}"> #set $id_sxe = '%s_%s_%sx%s' % ($cur_result['indexer'], $cur_result['showid'], $cur_result['season'], $cur_result['episode'])
<div class="$show_div" id="listing-${cur_result['showid']}" data-show-id="$id_sxe">
<div class="tvshowDiv"> <div class="tvshowDiv">
<table width="100%" border="0" cellpadding="0" cellspacing="0"> <table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr> <tr>
@ -614,7 +619,7 @@
<a href="<%= anon_url(cur_result['imdb_url']) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false" title="${cur_result['imdb_url']}"><img alt="[$sickbeard.indexerApi(INDEXER_IMDB).name]" height="16" width="16" src="$sbRoot/images/$sickbeard.indexerApi(INDEXER_IMDB).config.get('icon')" /></a> <a href="<%= anon_url(cur_result['imdb_url']) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false" title="${cur_result['imdb_url']}"><img alt="[$sickbeard.indexerApi(INDEXER_IMDB).name]" height="16" width="16" src="$sbRoot/images/$sickbeard.indexerApi(INDEXER_IMDB).config.get('icon')" /></a>
#end if #end if
<a href="<%= anon_url(sickbeard.indexerApi(cur_indexer).config['show_url'] % cur_result['showid']) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false" title="${sickbeard.indexerApi($cur_indexer).config['show_url'] % cur_result['showid']}"><img alt="$sickbeard.indexerApi($cur_indexer).name" height="16" width="16" src="$sbRoot/images/$sickbeard.indexerApi($cur_indexer).config['icon']" /></a> <a href="<%= anon_url(sickbeard.indexerApi(cur_indexer).config['show_url'] % cur_result['showid']) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false" title="${sickbeard.indexerApi($cur_indexer).config['show_url'] % cur_result['showid']}"><img alt="$sickbeard.indexerApi($cur_indexer).name" height="16" width="16" src="$sbRoot/images/$sickbeard.indexerApi($cur_indexer).config['icon']" /></a>
<span><a href="$sbRoot/home/searchEpisode?show=${cur_result['showid']}&amp;season=$cur_result['season']&amp;episode=$cur_result['episode']" title="Manual Search" id="forceUpdate-${cur_result['showid']}" class="epSearch forceUpdate"><img alt="[search]" height="16" width="16" src="$sbRoot/images/search16.png" id="forceUpdateImage-${cur_result['showid']}" /></a></span> <span><a class="ep-search" href="$sbRoot/home/episode_search?show=${cur_result['showid']}&amp;season=$cur_result['season']&amp;episode=$cur_result['episode']" title="Manual Search"><img title="[search]" alt="[search]" height="16" width="16" src="$sbRoot/images/search16.png" /></a></span>
</span> </span>
</div> </div>

View file

@ -101,7 +101,7 @@
#slurp #slurp
#set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($ep['status'])) #set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($ep['status']))
#if Quality.NONE != $curQuality #if Quality.NONE != $curQuality
<td class="col-status">#if $SUBTITLED == $curStatus#<span class="addQTip" title="$statusStrings[$curStatus]"><i class="sgicon-subtitles" style="vertical-align:middle"></i></span>#else#$statusStrings[$curStatus].replace('Downloaded', '')#end if# <span class="quality $Quality.get_quality_css($curQuality)#if $downloaded# addQTip" title="$downloaded#end if#">$Quality.get_quality_ui($curQuality)</span></td> <td class="col-status">#if $SUBTITLED == $curStatus#<span class="addQTip" title="$statusStrings[$curStatus]"><i class="sgicon-subtitles" style="vertical-align:middle"></i></span>#else#$statusStrings[$curStatus].replace('Downloaded', '')#end if# #if 'Unknown' != $statusStrings[$curStatus]#<span class="quality $Quality.get_quality_css($curQuality)#if $downloaded# addQTip" title="$downloaded#end if#">$Quality.get_quality_ui($curQuality)</span>#end if#</td>
#else #else
<td class="col-status">$statusStrings[$curStatus].replace('SD DVD', 'SD DVD/BR/BD')</td> <td class="col-status">$statusStrings[$curStatus].replace('SD DVD', 'SD DVD/BR/BD')</td>
#end if #end if
@ -109,9 +109,9 @@
#if 0 != int($ep['season']) #if 0 != int($ep['season'])
#set $status = $Quality.splitCompositeStatus(int($ep['status']))[0] #set $status = $Quality.splitCompositeStatus(int($ep['status']))[0]
#if ($status in $SNATCHED_ANY + [$DOWNLOADED, $ARCHIVED]) and $sg_var('USE_FAILED_DOWNLOADS') #if ($status in $SNATCHED_ANY + [$DOWNLOADED, $ARCHIVED]) and $sg_var('USE_FAILED_DOWNLOADS')
<a class="epRetry" id="$ep_str" name="$ep_str" href="$sbRoot/home/retryEpisode?show=$show.indexerid&amp;season=$ep['season']&amp;episode=$ep['episode']"><img src="$sbRoot/images/search16.png" height="16" alt="retry" title="Retry download"></a> <a class="ep-retry" href="$sbRoot/home/episode_retry?show=$show.indexerid&amp;season=$ep['season']&amp;episode=$ep['episode']"><img src="$sbRoot/images/search16.png" height="16" alt="retry" title="Retry download"></a>
#else #else
<a class="epSearch" id="$ep_str" name="$ep_str" href="$sbRoot/home/searchEpisode?show=$show.indexerid&amp;season=$ep['season']&amp;episode=$ep['episode']"><img src="$sbRoot/images/search16.png" width="16" height="16" alt="search" title="Manual search"></a> <a class="ep-search" href="$sbRoot/home/episode_search?show=$show.indexerid&amp;season=$ep['season']&amp;episode=$ep['episode']"><img src="$sbRoot/images/search16.png" width="16" height="16" alt="search" title="Manual search"></a>
#end if #end if
#end if #end if
#slurp #slurp

View file

@ -1,194 +1,235 @@
var search_status_url = sbRoot + '/home/getManualSearchStatus'; /** @namespace $.SickGear.Root */
/** @namespace data.episodes */
/** @namespace ep.showindexer */
/** @namespace ep.showindexid */
/** @namespace ep.season */
/** @namespace ep.episode */
/** @namespace ep.searchstate */
/** @namespace ep.status */
/** @namespace ep.quality */
/** @namespace ep.retrystate */
/** @namespace ep.statusoverview */
var dev = !1,
logInfo = dev ? console.info.bind(window.console) : function() {},
logErr = dev ? console.error.bind(window.console) : function() {};
PNotify.prototype.options.maxonscreen = 5; PNotify.prototype.options.maxonscreen = 5;
$.fn.manualSearches = [];
function check_manual_searches() {
var poll_interval = 5000;
$.ajax({
url: search_status_url + '?show=' + $('#showID').val(),
success: function (data) {
if (data.episodes) {
poll_interval = 5000;
}
else {
poll_interval = 15000;
}
updateImages(data);
//cleanupManualSearches(data);
},
error: function () {
poll_interval = 30000;
},
type: "GET",
dataType: "json",
complete: function () {
setTimeout(check_manual_searches, poll_interval);
},
timeout: 15000 // timeout every 15 secs
});
}
function updateImages(data) {
$.each(data.episodes, function (name, ep) {
console.debug(ep.searchstatus);
// Get td element for current ep
var loadingImage = 'loading16.gif';
var queuedImage = 'queued.png';
var searchImage = 'search16.png';
var status = null;
//Try to get the <a> Element
el=$('a[id=' + ep.season + 'x' + ep.episode+']');
img=el.children('img');
parent=el.parent();
if (el) {
if (ep.searchstatus == 'searching') {
//el=$('td#' + ep.season + 'x' + ep.episode + '.search img');
img.attr('title','Searching');
img.prop('alt','searching');
img.attr('src',sbRoot+'/images/' + loadingImage);
disableLink(el);
// Update Status and Quality
var rSearchTerm = /(\w+)\s\((.+?)\)/;
HtmlContent = ep.searchstatus;
}
else if (ep.searchstatus == 'queued') {
//el=$('td#' + ep.season + 'x' + ep.episode + '.search img');
img.attr('title','Queued');
img.prop('alt','queued');
img.attr('src',sbRoot+'/images/' + queuedImage );
disableLink(el);
HtmlContent = ep.searchstatus;
}
else if (ep.searchstatus == 'finished') {
//el=$('td#' + ep.season + 'x' + ep.episode + '.search img');
var imgParent = img.parent();
if (ep.retrystatus) {
imgParent.attr('class','epRetry');
imgParent.attr('href', imgParent.attr('href').replace('/home/searchEpisode?', '/home/retryEpisode?'));
img.attr('title','Retry download');
img.prop('alt', 'retry download');
}
else {
imgParent.attr('class','epSearch');
imgParent.attr('href', imgParent.attr('href').replace('/home/retryEpisode?', '/home/searchEpisode?'));
img.attr('title','Manual search');
img.prop('alt', 'manual search');
}
img.attr('src',sbRoot+'/images/' + searchImage);
enableLink(el);
// Update Status and Quality
parent.closest('tr').removeClass('skipped wanted qual good unaired snatched').addClass(ep.statusoverview);
var rSearchTerm = /(\w+)\s\((.+?)\)/;
HtmlContent = ep.status.replace(rSearchTerm,"$1"+' <span class="quality '+ep.quality+'">'+"$2"+'</span>');
}
// update the status column if it exists
parent.siblings('.col-status').html(HtmlContent)
}
});
}
$(document).ready(function () { $(document).ready(function () {
check_manual_searches(); ajaxConsumer.checkManualSearches();
}); });
function enableLink(el) { var ajaxConsumer = function () {
el.on('click.disabled', false); var that = this;
el.attr('enableClick', '1'); that.timeoutId = 0;
el.fadeTo("fast", 1) that.pollInterval = 0;
logInfo('init ajaxConsumer');
return {
checkManualSearches : function () {
logInfo('ajaxConsumer.checkManualSearches()');
var showId = $('#showID').val();
$.getJSON({
url: $.SickGear.Root + '/home/search_q_progress' + (/undefined/i.test(showId) ? '' : '?show=' + showId),
timeout: 15000 // timeout request after 15 secs
})
.done(function (data) {
logInfo('search_q_progress.success(data)', data);
if (!data.episodes || 0 === data.episodes.length) {
imgRestore();
}
// using 5s as a reasonable max. when updating images from historical statuses after a page refresh
that.pollInterval = data.episodes && data.episodes.length
? (uiUpdateComplete(data) ? 5000 : 1000) : 10000; // 10000/0
})
.fail(function () {
logErr('search_q_progress.error()');
that.pollInterval = 30000;
})
.always(function (jqXHR, textStatus) {
logInfo('search_q_progress.complete(textStatus)', '\'' + textStatus + '\'.');
clearTimeout(that.timeoutId);
if (that.pollInterval)
that.timeoutId = setTimeout(ajaxConsumer.checkManualSearches, that.pollInterval);
logInfo(that.pollInterval ? '^-- ' + that.pollInterval/1000 + 's to next work' : '^-- no more work');
logInfo('====');
});
}
};
}();
function uiUpdateComplete(data) {
var isFinished = !0;
$.each(data.episodes, function (name, ep) {
var sxe = ep.season + 'x' + ep.episode,
displayShow$ = $('#' + sxe).closest('tr'),
episodeView$ = $('[data-show-id="' + ep.showindexer + '_' + ep.showindexid + '_' + sxe + '"]'),
link$ = (displayShow$.length ? displayShow$ : episodeView$).find('.ep-search, .ep-retry'),
uiOptions = $.ajaxEpSearch.defaults;
logInfo('^-- data item', name, ep.searchstate, ep.showindexid, sxe, ep.statusoverview);
if (link$.length) {
var htmlContent = '', imgTip, imgCls;
switch (ep.searchstate) {
case 'searching':
isFinished = !1;
imgUpdate(link$, 'Searching', uiOptions.loadingImage);
disableLink(link$);
htmlContent = '[' + ep.searchstate + ']';
break;
case 'queued':
isFinished = !1;
imgUpdate(link$, 'Queued', uiOptions.queuedImage);
disableLink(link$);
htmlContent = '[' + ep.searchstate + ']';
break;
case 'finished':
var attrName = !!getAttr(link$, 'href') ? 'href' : 'data-href', href = getAttr(link$, attrName);
if (ep.retrystate) {
imgTip = 'Click to retry download';
link$.attr('class', 'ep-retry').attr(attrName, href.replace('search', 'retry'));
} else {
imgTip = 'Click for manual search';
link$.attr('class', 'ep-search').attr(attrName, href.replace('retry', 'search'));
}
if (/good/i.test(ep.statusoverview)) {
imgCls = uiOptions.searchImage;
} else if (/snatched/i.test(ep.statusoverview)) {
imgCls = uiOptions.imgYes;
} else {
imgTip = 'Last manual search failed. Click to try again';
imgCls = uiOptions.imgNo;
}
imgUpdate(link$, imgTip, imgCls);
enableLink(link$);
// update row status
if (ep.statusoverview) {
link$.closest('tr')
.removeClass('skipped wanted qual good unaired snatched')
.addClass(ep.statusoverview);
}
// update quality text for status column
var rSearchTerm = /(\w+)\s\((.+?)\)/;
htmlContent = ep.status.replace(rSearchTerm,
'$1' + ' <span class="quality ' + ep.quality + '">' + '$2' + '</span>');
// remove backed vars
link$.removeAttr('data-status data-imgclass');
}
// update the status area
link$.closest('.col-search').siblings('.col-status').html(htmlContent);
}
});
return isFinished;
} }
function disableLink(el) { function enableLink(el$) {
el.off('click.disabled'); el$.attr('href', el$.attr('data-href')).removeAttr('data-href').fadeTo('fast', 1);
el.attr('enableClick', '0');
el.fadeTo("fast", .5)
} }
(function(){ function disableLink(el$) {
el$.attr('data-href', el$.attr('href')).removeAttr('href').fadeTo('fast', .7);
}
function getAttr(el$, name) {
return el$.is('[' + name + ']') ? el$.attr(name) : !1;
}
function imgUpdate(link$, tip, cls) {
link$.find('img').attr('src', '').attr('title', tip).prop('alt', '')
.removeClass('spinner queued search no yes').addClass(cls);
}
function imgRestore() {
$('a[data-status]').each(function() {
$(this).closest('.col-search').siblings('.col-status').html($(this).attr('data-status'));
imgUpdate($(this),
getAttr($(this), 'data-imgtitle'),
getAttr($(this), 'data-imgclass') || $.ajaxEpSearch.defaults.searchImage);
$(this).removeAttr('data-status data-imgclass data-imgtitle');
});
}
(function() {
$.ajaxEpSearch = { $.ajaxEpSearch = {
defaults: { defaults: {
size: 16, size: 16,
colorRow: false, colorRow: !1,
loadingImage: 'loading16.gif', loadingImage: 'spinner',
queuedImage: 'queued.png', queuedImage: 'queued',
noImage: 'no16.png', searchImage: 'search',
yesImage: 'yes16.png' imgNo: 'no',
} imgYes: 'yes'
}
}; };
$.fn.ajaxEpSearch = function(options){ $.fn.ajaxEpSearch = function(uiOptions) {
options = $.extend({}, $.ajaxEpSearch.defaults, options); uiOptions = $.extend( {}, $.ajaxEpSearch.defaults, uiOptions);
$('.epSearch, .epRetry').click(function(event){ $('.ep-search, .ep-retry').on('click', function(event) {
event.preventDefault(); event.preventDefault();
logInfo(($(this).hasClass('ep-search') ? 'Search' : 'Retry') + ' clicked');
// Check if we have disabled the click // check if we have disabled the click
if ( $(this).attr('enableClick') == '0' ) { if (!!getAttr($(this), 'data-href')) {
console.debug("Already queued, not downloading!"); logInfo('Already queued, not downloading!');
return false; return !1;
} }
if ( $(this).attr('class') == "epRetry" ) { if ($(this).hasClass('ep-retry')
if ( !confirm("Mark download as bad and retry?") ) && !confirm('Mark download as bad and retry?')) {
return false; return !1;
}; }
var parent = $(this).parent(); var link$ = $(this), img$ = link$.find('img'), img = ['Failed', uiOptions.imgNo], imgCls;
// backup ui vars
if (link$.closest('.col-search') && link$.closest('.col-search').siblings('.col-status')) {
link$.attr('data-status', link$.closest('.col-search').siblings('.col-status').html().trim());
}
link$.attr('data-imgtitle', getAttr(img$, 'title'));
if (imgCls = getAttr(img$, 'class')) {
link$.attr('data-imgclass', imgCls.trim());
}
// Create var for anchor imgUpdate(link$, 'Loading', uiOptions.loadingImage);
link = $(this);
// Create var for img under anchor and set options for the loading gif $.getJSON({url: $(this).attr('href'), timeout: 15000})
img=$(this).children('img'); .done(function(data) {
img.attr('title','loading'); logInfo('getJSON() data...', data);
img.prop('alt','');
img.attr('src',sbRoot+'/images/' + options.loadingImage);
// if failed, replace success/queued with initiated red X/No
if ('failure' !== data.result) {
// otherwise, queued successfully
$.getJSON($(this).attr('href'), function(data){ // update ui status
link$.closest('.col-search').siblings('.col-status').html('[' + data.result + ']');
// if they failed then just put the red X // prevent further interaction
if (data.result == 'failure') { disableLink(link$);
img_name = options.noImage;
img_result = 'failed';
// if the snatch was successful then apply the corresponding class and fill in the row appropriately img = 'queueing' === data.result
} else { ? ['Queueing', uiOptions.queuedImage]
img_name = options.loadingImage; : ['Searching', uiOptions.loadingImage];
img_result = 'success'; }
// color the row
if (options.colorRow)
parent.parent().removeClass('skipped wanted qual good unaired').addClass('snatched');
// applying the quality class
var rSearchTerm = /(\w+)\s\((.+?)\)/;
HtmlContent = data.result.replace(rSearchTerm,"$1"+' <span class="quality '+data.quality+'">'+"$2"+'</span>');
// update the status column if it exists
parent.siblings('.col-status').html(HtmlContent)
// Only if the queing was succesfull, disable the onClick event of the loading image
disableLink(link);
}
// put the corresponding image as the result of queuing of the manual search // update ui image
img.attr('title',img_result); imgUpdate(link$, img[0], img[1]);
img.prop('alt',img_result); ajaxConsumer.checkManualSearches();
img.attr('height', options.size); })
img.attr('src',sbRoot+"/images/"+img_name); .fail(function() { imgRestore(); });
});
//
// don't follow the link // prevent following the clicked link
return false; return !1;
}); });
} };
})(); })();

View file

@ -173,7 +173,7 @@ $(document).ready(function() {
qTips($('.addQTip')); qTips($('.addQTip'));
function table_init(table$) { function table_init(table$) {
$('#sbRoot').ajaxEpSearch({'colorRow': true}); $('#sbRoot').ajaxEpSearch();
$('#sbRoot').ajaxEpSubtitlesSearch(); $('#sbRoot').ajaxEpSubtitlesSearch();
if ($.SickGear.config.useFuzzy) { if ($.SickGear.config.useFuzzy) {

View file

@ -22,6 +22,7 @@ import traceback
import threading import threading
import datetime import datetime
import re import re
import copy
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, \
@ -91,7 +92,7 @@ class SearchQueue(generic_queue.GenericQueue):
def get_queued_manual(self, show): def get_queued_manual(self, show):
""" """
Returns None or List of copies of all show related items in manual or failed queue Returns None or List of base info items of all show related items in manual or failed queue
:param show: show indexerid or None for all q items :param show: show indexerid or None for all q items
:type show: String or None :type show: String or None
:return: List with 0 or more items :return: List with 0 or more items
@ -107,10 +108,10 @@ class SearchQueue(generic_queue.GenericQueue):
def get_current_manual_item(self, show): def get_current_manual_item(self, show):
""" """
Returns a static copy of the currently active manual search item Returns a base info item of the currently active manual search item
:param show: show indexerid or None for all q items :param show: show indexerid or None for all q items
:type show: String or None :type show: String or None
:return: copy of ManualSearchQueueItem or FailedQueueItem or None :return: base info item of ManualSearchQueueItem or FailedQueueItem or None
""" """
with self.lock: with self.lock:
if self.currentItem and isinstance(self.currentItem, (ManualSearchQueueItem, FailedQueueItem)) \ if self.currentItem and isinstance(self.currentItem, (ManualSearchQueueItem, FailedQueueItem)) \
@ -412,46 +413,38 @@ class BaseSearchQueueItem(generic_queue.QueueItem):
super(BaseSearchQueueItem, self).__init__(name, action_id) super(BaseSearchQueueItem, self).__init__(name, action_id)
self.segment = segment self.segment = segment
self.show = show self.show = show
self.added_dt = None
self.success = None self.success = None
self.snatched_eps = set([]) self.snatched_eps = set([])
def base_info(self): def base_info(self):
o = SimpleNamespace() return SimpleNamespace(
o.success = self.success success=self.success,
o.show = SimpleNamespace() added_dt=self.added_dt,
o.show.indexer = self.show.indexer snatched_eps=copy.deepcopy(self.snatched_eps),
o.show.indexerid = self.show.indexerid show=SimpleNamespace(
o.show.quality = self.show.quality indexer=self.show.indexer, indexerid=self.show.indexerid,
o.show.upgrade_once = self.show.upgrade_once quality=self.show.quality, upgrade_once=self.show.upgrade_once),
sl = [] segment=[SimpleNamespace(
for s in ([self.segment], self.segment)[isinstance(self.segment, list)]: season=s.season, episode=s.episode, status=s.status,
eo = SimpleNamespace() show=SimpleNamespace(
eo.episode = s.episode indexer=s.show.indexer, indexerid=s.show.indexerid,
eo.season = s.season quality=s.show.quality, upgrade_once=s.show.upgrade_once
eo.status = s.status )) for s in ([self.segment], self.segment)[isinstance(self.segment, list)]])
eo.show = SimpleNamespace()
eo.show.indexer = s.show.indexer
eo.show.indexerid = s.show.indexerid
eo.show.quality = s.show.quality
eo.show.upgrade_once = s.show.upgrade_once
sl.append(eo)
o.segment = sl
return o # def copy(self, deepcopy_obj=None):
# if not isinstance(deepcopy_obj, list):
def copy(self, deepcopy_obj=None): # deepcopy_obj = []
if not isinstance(deepcopy_obj, list): # deepcopy_obj += ['segment']
deepcopy_obj = [] # same_show = True
deepcopy_obj += ['segment'] # if (isinstance(self.segment, list) and getattr(self.segment[0], 'show') is not self.show) \
same_show = True # or getattr(self.segment, 'show') is not self.show:
if (isinstance(self.segment, list) and getattr(self.segment[0], 'show') is not self.show) \ # same_show = False
or getattr(self.segment, 'show') is not self.show: # deepcopy_obj += ['show']
same_show = False # n_o = super(BaseSearchQueueItem, self).copy(deepcopy_obj)
deepcopy_obj += ['show'] # if same_show:
n_o = super(BaseSearchQueueItem, self).copy(deepcopy_obj) # n_o.show = (getattr(n_o.segment, 'show'), getattr(n_o.segment[0], 'show'))[isinstance(n_o.segment, list)]
if same_show: # return n_o
n_o.show = (getattr(n_o.segment, 'show'), getattr(n_o.segment[0], 'show'))[isinstance(n_o.segment, list)]
return n_o
class ManualSearchQueueItem(BaseSearchQueueItem): class ManualSearchQueueItem(BaseSearchQueueItem):
@ -460,7 +453,6 @@ class ManualSearchQueueItem(BaseSearchQueueItem):
self.priority = generic_queue.QueuePriorities.HIGH self.priority = generic_queue.QueuePriorities.HIGH
self.name = 'MANUAL-%s' % show.indexerid self.name = 'MANUAL-%s' % show.indexerid
self.started = None self.started = None
self.added_dt = None
def run(self): def run(self):
generic_queue.QueueItem.run(self) generic_queue.QueueItem.run(self)
@ -494,7 +486,7 @@ class ManualSearchQueueItem(BaseSearchQueueItem):
finally: finally:
# Keep a list with the last executed searches # Keep a list with the last executed searches
fifo(MANUAL_SEARCH_HISTORY, self) fifo(MANUAL_SEARCH_HISTORY, self.base_info())
if self.success is None: if self.success is None:
self.success = False self.success = False
@ -554,7 +546,6 @@ class FailedQueueItem(BaseSearchQueueItem):
self.priority = generic_queue.QueuePriorities.HIGH self.priority = generic_queue.QueuePriorities.HIGH
self.name = 'RETRY-%s' % show.indexerid self.name = 'RETRY-%s' % show.indexerid
self.started = None self.started = None
self.added_dt = None
def run(self): def run(self):
generic_queue.QueueItem.run(self) generic_queue.QueueItem.run(self)
@ -596,7 +587,7 @@ class FailedQueueItem(BaseSearchQueueItem):
finally: finally:
# Keep a list with the last executed searches # Keep a list with the last executed searches
fifo(MANUAL_SEARCH_HISTORY, self) fifo(MANUAL_SEARCH_HISTORY, self.base_info())
if self.success is None: if self.success is None:
self.success = False self.success = False

View file

@ -2797,69 +2797,57 @@ class Home(MainHandler):
seen_eps = set([]) seen_eps = set([])
# Queued searches # Queued searches
queued_items = sickbeard.searchQueueScheduler.action.get_queued_manual(show) queued = sickbeard.searchQueueScheduler.action.get_queued_manual(show)
# Active search # Active search
active_item = sickbeard.searchQueueScheduler.action.get_current_manual_item(show) active = sickbeard.searchQueueScheduler.action.get_current_manual_item(show)
# Finished searches # Finished searches
sickbeard.search_queue.remove_old_fifo(sickbeard.search_queue.MANUAL_SEARCH_HISTORY) sickbeard.search_queue.remove_old_fifo(sickbeard.search_queue.MANUAL_SEARCH_HISTORY)
finished_items = sickbeard.search_queue.MANUAL_SEARCH_HISTORY results = sickbeard.search_queue.MANUAL_SEARCH_HISTORY
progress = 'queued' for item in filter(lambda q: hasattr(q, 'segment'), queued):
for thread in queued_items: for ep_base in item.segment:
if hasattr(thread, 'segment'): ep, uniq_sxe = self.prepare_episode(ep_base, 'queued')
for ep_obj in thread.segment: episodes.append(ep)
ep, uniq_sxe = self.prepare_episode(ep_obj.show, ep_obj, progress) seen_eps.add(uniq_sxe)
episodes.append(ep)
seen_eps.add(uniq_sxe)
if active_item: if active and hasattr(active, 'segment'):
thread = active_item
episode_params = dict(([('searchstate', 'finished'), ('statusoverview', True)], episode_params = dict(([('searchstate', 'finished'), ('statusoverview', True)],
[('searchstate', 'searching'), ('statusoverview', False)])[None is thread.success], [('searchstate', 'searching'), ('statusoverview', False)])[None is active.success],
retrystate=True) retrystate=True)
if hasattr(thread, 'segment'): for ep_base in active.segment:
for ep_obj in thread.segment: ep, uniq_sxe = self.prepare_episode(ep_base, **episode_params)
ep, uniq_sxe = self.prepare_episode(ep_obj.show, ep_obj, **episode_params) episodes.append(ep)
episodes.append(ep) seen_eps.add(uniq_sxe)
seen_eps.add(uniq_sxe)
episode_params = dict(searchstate='finished', retrystate=True, statusoverview=True) episode_params = dict(searchstate='finished', retrystate=True, statusoverview=True)
for thread in finished_items: for item in filter(lambda r: hasattr(r, 'segment') and (not show or show == str(r.show.indexerid)), results):
if not isinstance(getattr(thread, 'segment'), list): for ep_base in filter(
if (not show or show == str(thread.show.indexerid)) and \ lambda e: (e.show.indexer, e.show.indexerid, e.season, e.episode) not in seen_eps, item.segment):
(thread.show.indexer, thread.show.indexerid, thread.segment.season, thread.segment.episode) \ ep, uniq_sxe = self.prepare_episode(ep_base, **episode_params)
not in seen_eps: episodes.append(ep)
ep, uniq_sxe = self.prepare_episode(thread.show, thread.segment, **episode_params) seen_eps.add(uniq_sxe)
episodes.append(ep)
seen_eps.add(uniq_sxe)
# These are only Failed Downloads/Retry SearchThreadItems.. lets loop through the segment/episodes for snatched in filter(lambda s: (s not in seen_eps), item.snatched_eps):
elif hasattr(thread, 'segment') and show == str(thread.show.indexerid): try:
for ep_obj in thread.segment: show = helpers.find_show_by_id(sickbeard.showList, dict({snatched[0]: snatched[1]}))
if (ep_obj.show.indexer, ep_obj.show.indexerid, ep_obj.season, ep_obj.episode) not in seen_eps: ep_obj = show.getEpisode(season=snatched[2], episode=snatched[3])
ep, uniq_sxe = self.prepare_episode(ep_obj.show, ep_obj, **episode_params) except (StandardError, Exception):
episodes.append(ep) continue
seen_eps.add(uniq_sxe) ep, uniq_sxe = self.prepare_episode(ep_obj, **episode_params)
for snatched in filter(lambda v: v not in seen_eps, thread.snatched_eps):
ep_obj = thread.show.getEpisode(season=snatched[2], episode=snatched[3])
ep, uniq_sxe = self.prepare_episode(thread.show, ep_obj, **episode_params)
episodes.append(ep) episodes.append(ep)
seen_eps.add(uniq_sxe) seen_eps.add(uniq_sxe)
return json.dumps(dict(episodes=episodes)) return json.dumps(dict(episodes=episodes))
@staticmethod @staticmethod
def prepare_episode(show, ep, searchstate, retrystate=False, statusoverview=False): def prepare_episode(ep, searchstate, retrystate=False, statusoverview=False):
""" """
Prepare episode data and its unique id Prepare episode data and its unique id
:param show: Show object :param ep: Episode structure containing the show that it relates to
:type show: TVShow object :type ep: TVEpisode object or Episode Base Namespace
:param ep: Episode object
:type ep: TVEpisode object
:param searchstate: Progress of search :param searchstate: Progress of search
:type searchstate: string :type searchstate: string
:param retrystate: True to add retrystate to data :param retrystate: True to add retrystate to data
@ -2877,7 +2865,7 @@ class Home(MainHandler):
quality_class = qualityPresetStrings[x] quality_class = qualityPresetStrings[x]
break break
ep_data = dict(showindexer=show.indexer, showindexid=show.indexerid, ep_data = dict(showindexer=ep.show.indexer, showindexid=ep.show.indexerid,
season=ep.season, episode=ep.episode, quality=quality_class, season=ep.season, episode=ep.episode, quality=quality_class,
searchstate=searchstate, status=statusStrings[ep.status]) searchstate=searchstate, status=statusStrings[ep.status])
if retrystate: if retrystate:
@ -2885,9 +2873,9 @@ class Home(MainHandler):
ep_data.update(dict(retrystate=sickbeard.USE_FAILED_DOWNLOADS and ep_status in retry_statuses)) ep_data.update(dict(retrystate=sickbeard.USE_FAILED_DOWNLOADS and ep_status in retry_statuses))
if statusoverview: if statusoverview:
ep_data.update(dict(statusoverview=Overview.overviewStrings[ ep_data.update(dict(statusoverview=Overview.overviewStrings[
helpers.getOverview(ep.status, show.quality, show.upgrade_once)])) helpers.getOverview(ep.status, ep.show.quality, ep.show.upgrade_once)]))
return ep_data, (show.indexer, show.indexerid, ep.season, ep.episode) return ep_data, (ep.show.indexer, ep.show.indexerid, ep.season, ep.episode)
def searchEpisodeSubtitles(self, show=None, season=None, episode=None): def searchEpisodeSubtitles(self, show=None, season=None, episode=None):
# retrieve the episode object and fail if we can't get one # retrieve the episode object and fail if we can't get one