From ac6d1f69d963d806e50025a1b6e8dae37c1979a0 Mon Sep 17 00:00:00 2001 From: JackDandy Date: Tue, 2 Oct 2018 20:03:01 +0100 Subject: [PATCH] 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. --- CHANGES.md | 4 + gui/slick/css/dark.css | 1 + gui/slick/css/style.css | 13 + gui/slick/interfaces/default/displayShow.tmpl | 4 +- gui/slick/interfaces/default/episodeView.tmpl | 23 +- .../interfaces/default/inc_displayShow.tmpl | 6 +- gui/slick/js/ajaxEpSearch.js | 389 ++++++++++-------- gui/slick/js/displayShow.js | 2 +- sickbeard/search_queue.py | 75 ++-- sickbeard/webserve.py | 78 ++-- 10 files changed, 319 insertions(+), 276 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 51779e36..2604399d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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] diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index be30683a..87f9bc40 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -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 } diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index c15fa3f9..19e48ea9 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -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{ diff --git a/gui/slick/interfaces/default/displayShow.tmpl b/gui/slick/interfaces/default/displayShow.tmpl index d28eb872..9bbd09e2 100644 --- a/gui/slick/interfaces/default/displayShow.tmpl +++ b/gui/slick/interfaces/default/displayShow.tmpl @@ -50,11 +50,11 @@ -
+
#if $has_art diff --git a/gui/slick/interfaces/default/episodeView.tmpl b/gui/slick/interfaces/default/episodeView.tmpl index 19a30db6..768a4b3f 100644 --- a/gui/slick/interfaces/default/episodeView.tmpl +++ b/gui/slick/interfaces/default/episodeView.tmpl @@ -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} +
#if $show_message
@@ -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) - + ## forced to use a div to wrap airdate, the column sort went crazy with a span
$sbdatetime.sbdatetime.sbfdatetime($cur_result['localtime']).decode($sickbeard.SYS_ENCODING)
$cur_result['localtime'].strftime('%Y%m%d%H%M') @@ -428,7 +432,7 @@ - [search] + [search] @@ -455,7 +459,7 @@ -
+ #set $id_sxe = '%s_%s_%sx%s' % ($cur_result['indexer'], $cur_result['showid'], $cur_result['season'], $cur_result['episode']) +
@@ -614,7 +619,7 @@ [$sickbeard.indexerApi(INDEXER_IMDB).name] #end if $sickbeard.indexerApi($cur_indexer).name - [search] + [search] diff --git a/gui/slick/interfaces/default/inc_displayShow.tmpl b/gui/slick/interfaces/default/inc_displayShow.tmpl index a65e1493..a96c9c53 100644 --- a/gui/slick/interfaces/default/inc_displayShow.tmpl +++ b/gui/slick/interfaces/default/inc_displayShow.tmpl @@ -101,7 +101,7 @@ #slurp #set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($ep['status'])) #if Quality.NONE != $curQuality - + #else #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') - retry + retry #else - search + search #end if #end if #slurp diff --git a/gui/slick/js/ajaxEpSearch.js b/gui/slick/js/ajaxEpSearch.js index aad1f07f..a7343d20 100644 --- a/gui/slick/js/ajaxEpSearch.js +++ b/gui/slick/js/ajaxEpSearch.js @@ -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 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"+' '+"$2"+''); - - } - // 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' + ' ' + '$2' + ''); + + // 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' - } + defaults: { + size: 16, + colorRow: !1, + loadingImage: 'spinner', + queuedImage: 'queued', + searchImage: 'search', + imgNo: 'no', + imgYes: 'yes' + } }; - $.fn.ajaxEpSearch = function(options){ - options = $.extend({}, $.ajaxEpSearch.defaults, options); - - $('.epSearch, .epRetry').click(function(event){ + $.fn.ajaxEpSearch = function(uiOptions) { + uiOptions = $.extend( {}, $.ajaxEpSearch.defaults, uiOptions); + + $('.ep-search, .ep-retry').on('click', function(event) { event.preventDefault(); - - // Check if we have disabled the click - if ( $(this).attr('enableClick') == '0' ) { - console.debug("Already queued, not downloading!"); - return false; - } - - if ( $(this).attr('class') == "epRetry" ) { - if ( !confirm("Mark download as bad and retry?") ) - return false; - }; - - 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'; + logInfo(($(this).hasClass('ep-search') ? 'Search' : 'Retry') + ' clicked'); - // 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"+' '+"$2"+''); - // 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); - } + // check if we have disabled the click + if (!!getAttr($(this), 'data-href')) { + logInfo('Already queued, not downloading!'); + return !1; + } - // 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; + 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; }); - } + }; })(); - diff --git a/gui/slick/js/displayShow.js b/gui/slick/js/displayShow.js index 52dde687..94ca22f8 100644 --- a/gui/slick/js/displayShow.js +++ b/gui/slick/js/displayShow.js @@ -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) { diff --git a/sickbeard/search_queue.py b/sickbeard/search_queue.py index ccf44bd9..d73e848f 100644 --- a/sickbeard/search_queue.py +++ b/sickbeard/search_queue.py @@ -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 diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index c08e28c8..b9206fa6 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -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) - episodes.append(ep) - seen_eps.add(uniq_sxe) + 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) - episodes.append(ep) - seen_eps.add(uniq_sxe) + 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) - episodes.append(ep) - seen_eps.add(uniq_sxe) + 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
#if $SUBTITLED == $curStatus##else#$statusStrings[$curStatus].replace('Downloaded', '')#end if# $Quality.get_quality_ui($curQuality)#if $SUBTITLED == $curStatus##else#$statusStrings[$curStatus].replace('Downloaded', '')#end if# #if 'Unknown' != $statusStrings[$curStatus]#$Quality.get_quality_ui($curQuality)#end if#$statusStrings[$curStatus].replace('SD DVD', 'SD DVD/BR/BD')