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
* Change requirements.txt Cheetah >= 3.1.0
* 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]

View file

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

View file

@ -2130,6 +2130,19 @@ td.col-search{
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-ep{width:1%;vertical-align:middle}
#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>
<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}
.tvshowImg {border:1px solid transparent;min-width:226px;min-hieght:332px}
</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">
#if $has_art

View file

@ -17,6 +17,7 @@
#set $restart = 'Restart SickGear for new features on this page'
#set $show_message = (None, $restart)[not $varExists('fanart')]
#set global $page_body_attr = 'episode-view" class="' + $css
#set theme_suffix = ('', '-dark')['dark' == $sg_str('THEME_NAME', 'dark')]
##
#import os.path
#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}
.desc{border-top:8px solid; border-bottom:0}
#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>
<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
<div class="alert alert-info" style="margin:-40px 0 50px">
@ -293,9 +296,9 @@
});
$(document).ready(function(){
sortList = [[$table_sort_header_codes[$sort], 0]];
#end raw
var sortList = [[$table_sort_header_codes[$sort], 0]];
#raw
$('#showListTable:has(tbody tr)').tablesorter({
widgets: ['stickyHeaders'],
sortList: sortList,
@ -381,9 +384,10 @@
#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'] //-->
<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
<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>
@ -428,7 +432,7 @@
</td>
<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>
</tr>
<!-- end $cur_result['show_name'] //-->
@ -455,7 +459,7 @@
<!--
#raw
$(document).ready(function(){
$('#sbRoot').ajaxEpSearch({'size': 16, 'loadingImage': 'loading16' + themeSpinner + '.gif'});
$('#sbRoot').ajaxEpSearch();
$('.ep_summary').hide();
$('.ep_summaryTrigger').click(function() {
$(this).next('.ep_summary').slideToggle('normal', function() {
@ -588,7 +592,8 @@
#end if
#slurp
<!-- 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">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<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>
#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>
<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>
</div>

View file

@ -101,7 +101,7 @@
#slurp
#set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($ep['status']))
#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
<td class="col-status">$statusStrings[$curStatus].replace('SD DVD', 'SD DVD/BR/BD')</td>
#end if
@ -109,9 +109,9 @@
#if 0 != int($ep['season'])
#set $status = $Quality.splitCompositeStatus(int($ep['status']))[0]
#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
<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
#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;
$.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 () {
check_manual_searches();
ajaxConsumer.checkManualSearches();
});
function enableLink(el) {
el.on('click.disabled', false);
el.attr('enableClick', '1');
el.fadeTo("fast", 1)
var ajaxConsumer = function () {
var that = this;
that.timeoutId = 0;
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) {
el.off('click.disabled');
el.attr('enableClick', '0');
el.fadeTo("fast", .5)
function enableLink(el$) {
el$.attr('href', el$.attr('data-href')).removeAttr('data-href').fadeTo('fast', 1);
}
(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 = {
defaults: {
size: 16,
colorRow: false,
loadingImage: 'loading16.gif',
queuedImage: 'queued.png',
noImage: 'no16.png',
yesImage: 'yes16.png'
colorRow: !1,
loadingImage: 'spinner',
queuedImage: 'queued',
searchImage: 'search',
imgNo: 'no',
imgYes: 'yes'
}
};
$.fn.ajaxEpSearch = function(options){
options = $.extend({}, $.ajaxEpSearch.defaults, options);
$.fn.ajaxEpSearch = function(uiOptions) {
uiOptions = $.extend( {}, $.ajaxEpSearch.defaults, uiOptions);
$('.epSearch, .epRetry').click(function(event){
$('.ep-search, .ep-retry').on('click', function(event) {
event.preventDefault();
logInfo(($(this).hasClass('ep-search') ? 'Search' : 'Retry') + ' clicked');
// Check if we have disabled the click
if ( $(this).attr('enableClick') == '0' ) {
console.debug("Already queued, not downloading!");
return false;
// check if we have disabled the click
if (!!getAttr($(this), 'data-href')) {
logInfo('Already queued, not downloading!');
return !1;
}
if ( $(this).attr('class') == "epRetry" ) {
if ( !confirm("Mark download as bad and retry?") )
return false;
if ($(this).hasClass('ep-retry')
&& !confirm('Mark download as bad and retry?')) {
return !1;
}
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());
}
imgUpdate(link$, 'Loading', uiOptions.loadingImage);
$.getJSON({url: $(this).attr('href'), timeout: 15000})
.done(function(data) {
logInfo('getJSON() data...', data);
// if failed, replace success/queued with initiated red X/No
if ('failure' !== data.result) {
// otherwise, queued successfully
// update ui status
link$.closest('.col-search').siblings('.col-status').html('[' + data.result + ']');
// prevent further interaction
disableLink(link$);
img = 'queueing' === data.result
? ['Queueing', uiOptions.queuedImage]
: ['Searching', uiOptions.loadingImage];
}
// update ui image
imgUpdate(link$, img[0], img[1]);
ajaxConsumer.checkManualSearches();
})
.fail(function() { imgRestore(); });
// prevent following the clicked link
return !1;
});
};
var parent = $(this).parent();
// Create var for anchor
link = $(this);
// Create var for img under anchor and set options for the loading gif
img=$(this).children('img');
img.attr('title','loading');
img.prop('alt','');
img.attr('src',sbRoot+'/images/' + options.loadingImage);
$.getJSON($(this).attr('href'), function(data){
// if they failed then just put the red X
if (data.result == 'failure') {
img_name = options.noImage;
img_result = 'failed';
// if the snatch was successful then apply the corresponding class and fill in the row appropriately
} else {
img_name = options.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
img.attr('title',img_result);
img.prop('alt',img_result);
img.attr('height', options.size);
img.attr('src',sbRoot+"/images/"+img_name);
});
//
// don't follow the link
return false;
});
}
})();

View file

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

View file

@ -22,6 +22,7 @@ import traceback
import threading
import datetime
import re
import copy
import sickbeard
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):
"""
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
:type show: String or None
:return: List with 0 or more items
@ -107,10 +108,10 @@ class SearchQueue(generic_queue.GenericQueue):
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
: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:
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)
self.segment = segment
self.show = show
self.added_dt = None
self.success = None
self.snatched_eps = set([])
def base_info(self):
o = SimpleNamespace()
o.success = self.success
o.show = SimpleNamespace()
o.show.indexer = self.show.indexer
o.show.indexerid = self.show.indexerid
o.show.quality = self.show.quality
o.show.upgrade_once = self.show.upgrade_once
sl = []
for s in ([self.segment], self.segment)[isinstance(self.segment, list)]:
eo = SimpleNamespace()
eo.episode = s.episode
eo.season = s.season
eo.status = s.status
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 SimpleNamespace(
success=self.success,
added_dt=self.added_dt,
snatched_eps=copy.deepcopy(self.snatched_eps),
show=SimpleNamespace(
indexer=self.show.indexer, indexerid=self.show.indexerid,
quality=self.show.quality, upgrade_once=self.show.upgrade_once),
segment=[SimpleNamespace(
season=s.season, episode=s.episode, status=s.status,
show=SimpleNamespace(
indexer=s.show.indexer, indexerid=s.show.indexerid,
quality=s.show.quality, upgrade_once=s.show.upgrade_once
)) for s in ([self.segment], self.segment)[isinstance(self.segment, list)]])
return o
def copy(self, deepcopy_obj=None):
if not isinstance(deepcopy_obj, list):
deepcopy_obj = []
deepcopy_obj += ['segment']
same_show = True
if (isinstance(self.segment, list) and getattr(self.segment[0], 'show') is not self.show) \
or getattr(self.segment, 'show') is not self.show:
same_show = False
deepcopy_obj += ['show']
n_o = super(BaseSearchQueueItem, self).copy(deepcopy_obj)
if same_show:
n_o.show = (getattr(n_o.segment, 'show'), getattr(n_o.segment[0], 'show'))[isinstance(n_o.segment, list)]
return n_o
# def copy(self, deepcopy_obj=None):
# if not isinstance(deepcopy_obj, list):
# deepcopy_obj = []
# deepcopy_obj += ['segment']
# same_show = True
# if (isinstance(self.segment, list) and getattr(self.segment[0], 'show') is not self.show) \
# or getattr(self.segment, 'show') is not self.show:
# same_show = False
# deepcopy_obj += ['show']
# n_o = super(BaseSearchQueueItem, self).copy(deepcopy_obj)
# if same_show:
# 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):
@ -460,7 +453,6 @@ class ManualSearchQueueItem(BaseSearchQueueItem):
self.priority = generic_queue.QueuePriorities.HIGH
self.name = 'MANUAL-%s' % show.indexerid
self.started = None
self.added_dt = None
def run(self):
generic_queue.QueueItem.run(self)
@ -494,7 +486,7 @@ class ManualSearchQueueItem(BaseSearchQueueItem):
finally:
# 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:
self.success = False
@ -554,7 +546,6 @@ class FailedQueueItem(BaseSearchQueueItem):
self.priority = generic_queue.QueuePriorities.HIGH
self.name = 'RETRY-%s' % show.indexerid
self.started = None
self.added_dt = None
def run(self):
generic_queue.QueueItem.run(self)
@ -596,7 +587,7 @@ class FailedQueueItem(BaseSearchQueueItem):
finally:
# 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:
self.success = False

View file

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