From 419e35f30069d4d0a7272dc71cff02833d06d353 Mon Sep 17 00:00:00 2001 From: KontiSR Date: Mon, 15 Sep 2014 09:23:55 +0200 Subject: [PATCH] sbRoot missing in some img url's Added queued.png image.. for manual searching Fixed issue where consecutive manual searches where not queued properly Added first backend processing for retrieving all queued searches. This should fix the frondend blocking when doing manual searches for episodes, because the backend is free sooner. It now only queues the search. Created a returning ajax call for getting a list of all searches in queue en running on the displayShow page. For the getManualSearchStatus() function, only use curItem from the ManualSearchQueueItem or FailedQueueItem threads. Conflicts: sickbeard/search_queue.py --- gui/slick/images/queued.png | Bin 0 -> 466 bytes gui/slick/interfaces/default/displayShow.tmpl | 4 +- gui/slick/js/ajaxEpSearch.js | 162 ++++++++++++++++-- gui/slick/js/displayShow.js | 2 +- sickbeard/search_queue.py | 41 ++++- sickbeard/webserve.py | 98 ++++++++--- 6 files changed, 268 insertions(+), 39 deletions(-) create mode 100644 gui/slick/images/queued.png diff --git a/gui/slick/images/queued.png b/gui/slick/images/queued.png new file mode 100644 index 0000000000000000000000000000000000000000..4e048113f15662dbf6a851a45588c6773e68a798 GIT binary patch literal 466 zcmV;@0WJQCP)f#dUcu9KPy3@a4Scd7k&jdEf7;IlV689>y_<8V<3IEi`pm zchN=*Ke311*sXAr=Q*!Nu!$?dUgAaU%Xk~j>#Fnp5cB9O@cn$9)sH99KJ0)!0p4RE z_CUDK72rm=TdAOps}iY|65(c%u?zsWvtPnrOxIKXFZ>Sqxl5BjWk1FdCNE@EAMrdR zTc|kID(89pOLdJp>iC~4^i;$L7{${9yMwU=slx`ocPV=*4I8fD-!fLy_)|2o_lzLp z$#%VHViS+yS~9Go5qt2AAbsJ6upGM+(4zoT7%7|<@T@qGVj|kvYKF}aD@8M3;Z5 #if int($epResult["season"]) != 0: #if ( int($epResult["status"]) in $Quality.SNATCHED or int($epResult["status"]) in $Quality.DOWNLOADED ) and $sickbeard.USE_FAILED_DOWNLOADS: - retry + " name="<%=str(epResult["season"]) +"x"+str(epResult["episode"]) %>" href="retryEpisode?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]">retry #else: - search + " name="<%=str(epResult["season"]) +"x"+str(epResult["episode"]) %>" href="searchEpisode?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]">search #end if #end if #if $sickbeard.USE_SUBTITLES and $show.subtitles and len(set(str($epResult["subtitles"]).split(',')).intersection(set($subtitles.wantedLanguages()))) < len($subtitles.wantedLanguages()) and $epResult["location"] diff --git a/gui/slick/js/ajaxEpSearch.js b/gui/slick/js/ajaxEpSearch.js index 5185b81b..ffe6f6f0 100644 --- a/gui/slick/js/ajaxEpSearch.js +++ b/gui/slick/js/ajaxEpSearch.js @@ -1,3 +1,112 @@ +var search_status_url = sbRoot + '/getManualSearchStatus'; +$.pnotify.defaults.width = "400px"; +$.pnotify.defaults.styling = "jqueryui"; +$.pnotify.defaults.history = false; +$.pnotify.defaults.shadow = false; +$.pnotify.defaults.delay = 4000; +$.pnotify.defaults.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_dddddd.gif'; + var queuedImage = 'queued.png'; + var searchImage = 'search32.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.attr('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.attr('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'); + img.attr('title','Searching'); + img.attr('alt','searching'); + img.parent().attr('class','epRetry'); + img.attr('src',sbRoot+'/images/' + searchImage); + enableLink(el); + + // Update Status and Quality + var rSearchTerm = /(\w+)\s\((.+?)\)/; + HtmlContent = ep.status.replace(rSearchTerm,"$1"+' '+"$2"+''); + + } + // update the status column if it exists + parent.siblings('.status_column').html(HtmlContent) + + } + + }); +} + +$(document).ready(function () { + + check_manual_searches(); + +}); + +function enableLink(el) { + el.on('click.disabled', false); + el.attr('enableClick', '1'); + el.fadeTo("fast", 1) +} + +function disableLink(el) { + el.off('click.disabled'); + el.attr('enableClick', '0'); + el.fadeTo("fast", .5) +} + (function(){ $.ajaxEpSearch = { @@ -5,6 +114,7 @@ size: 16, colorRow: false, loadingImage: 'loading16_dddddd.gif', + queuedImage: 'queued.png', noImage: 'no16.png', yesImage: 'yes16.png' } @@ -13,22 +123,42 @@ $.fn.ajaxEpSearch = function(options){ options = $.extend({}, $.ajaxEpSearch.defaults, options); - $('.epSearch').click(function(){ - var parent = $(this).parent(); + $('.epSearch').click(function(event){ + event.preventDefault(); - // put the ajax spinner (for non white bg) placeholder while we wait - parent.empty(); - parent.append($("").attr({"src": sbRoot+"/images/"+options.loadingImage, "height": options.size, "alt": "", "title": "loading"})); + // 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.attr('alt',''); + img.attr('src',sbRoot+'/images/' + options.loadingImage); + $.getJSON($(this).attr('href'), function(data){ - // if they failed then just put the red X + + // 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.yesImage; + img_name = options.loadingImage; img_result = 'success'; // color the row if (options.colorRow) @@ -37,16 +167,22 @@ var rSearchTerm = /(\w+)\s\((.+?)\)/; HtmlContent = data.result.replace(rSearchTerm,"$1"+' '+"$2"+''); // update the status column if it exists - parent.siblings('.status_column').html(HtmlContent) + parent.siblings('.status_column').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 for the the row - parent.empty(); - parent.append($("").attr({"src": sbRoot+"/images/"+img_name, "height": options.size, "alt": img_result, "title": img_result})); + // put the corresponding image as the result of queuing of the manual search + img.attr('title',img_result); + img.attr('alt',img_result); + img.attr('height', options.size); + img.attr('src',sbRoot+"/images/"+img_name); }); - - // fon't follow the link + // + + // don't follow the link return false; }); } })(); + diff --git a/gui/slick/js/displayShow.js b/gui/slick/js/displayShow.js index 85879664..0abf94d9 100644 --- a/gui/slick/js/displayShow.js +++ b/gui/slick/js/displayShow.js @@ -1,7 +1,7 @@ $(document).ready(function () { $('#sbRoot').ajaxEpSearch({'colorRow': true}); - $('#sbRoot').ajaxEpRetry({'colorRow': true}); + //$('#sbRoot').ajaxEpRetry({'colorRow': true}); $('#sbRoot').ajaxEpSubtitlesSearch(); diff --git a/sickbeard/search_queue.py b/sickbeard/search_queue.py index 85bae16b..27b429dd 100644 --- a/sickbeard/search_queue.py +++ b/sickbeard/search_queue.py @@ -37,6 +37,8 @@ DAILY_SEARCH = 20 FAILED_SEARCH = 30 MANUAL_SEARCH = 40 +MANUAL_SEARCH_HISTORY = [] +MANUAL_SEARCH_HISTORY_SIZE = 100 class SearchQueue(generic_queue.GenericQueue): def __init__(self): @@ -54,7 +56,23 @@ class SearchQueue(generic_queue.GenericQueue): if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and cur_item.segment == segment: return True return False - + + def is_show_in_queue(self, show): + for cur_item in self.queue: + if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and cur_item.show.indexerid == show: + return True + return False + + def get_all_ep_from_queue(self, show): + ep_obj_list = [] + for cur_item in self.queue: + if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)) and str(cur_item.show.indexerid) == show: + ep_obj_list.append(cur_item) + + if ep_obj_list: + return ep_obj_list + return False + def pause_backlog(self): self.min_priority = generic_queue.QueuePriorities.HIGH @@ -65,6 +83,12 @@ class SearchQueue(generic_queue.GenericQueue): # backlog priorities are NORMAL, this should be done properly somewhere return self.min_priority >= generic_queue.QueuePriorities.NORMAL + def is_manualsearch_in_progress(self): + for cur_item in self.queue + [self.currentItem]: + if isinstance(cur_item, (ManualSearchQueueItem, FailedQueueItem)): + return True + return False + def is_backlog_in_progress(self): for cur_item in self.queue + [self.currentItem]: if isinstance(cur_item, BacklogQueueItem): @@ -140,12 +164,15 @@ class ManualSearchQueueItem(generic_queue.QueueItem): self.success = None self.show = show self.segment = segment + self.started = None def run(self): generic_queue.QueueItem.run(self) try: logger.log("Beginning manual search for: [" + self.segment.prettyName() + "]") + self.started = True + searchResult = search.searchProviders(self.show, [self.segment], True) if searchResult: @@ -164,7 +191,10 @@ class ManualSearchQueueItem(generic_queue.QueueItem): except Exception: logger.log(traceback.format_exc(), logger.DEBUG) - + + ### Keep a list with the 100 last executed searches + fifo(MANUAL_SEARCH_HISTORY, self, MANUAL_SEARCH_HISTORY_SIZE) + if self.success is None: self.success = False @@ -245,4 +275,9 @@ class FailedQueueItem(generic_queue.QueueItem): if self.success is None: self.success = False - self.finish() \ No newline at end of file + self.finish() + +def fifo(myList, item, maxSize = 100): + if len(myList) >= maxSize: + myList.pop(0) + myList.append(item) \ No newline at end of file diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 83e833bd..eeb32fc7 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -4307,10 +4307,9 @@ class Home(MainHandler): root_ep_obj.rename() redirect("/home/displayShow?show=" + show) - - + def searchEpisode(self, show=None, season=None, episode=None): - + # retrieve the episode object and fail if we can't get one ep_obj = _getEpisode(show, season, episode) if isinstance(ep_obj, str): @@ -4318,28 +4317,87 @@ class Home(MainHandler): # make a queue item for it and put it on the queue ep_queue_item = search_queue.ManualSearchQueueItem(ep_obj.show, ep_obj) + sickbeard.searchQueueScheduler.action.add_item(ep_queue_item) # @UndefinedVariable - - # wait until the queue item tells us whether it worked or not - while ep_queue_item.success is None: # @UndefinedVariable - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - - # return the correct json value + if ep_queue_item.success: - # Find the quality class for the episode - quality_class = Quality.qualityStrings[Quality.UNKNOWN] - ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status) - for x in (SD, HD720p, HD1080p): - if ep_quality in Quality.splitQuality(x)[0]: - quality_class = qualityPresetStrings[x] - break + return returnManualSearchResult(ep_queue_item) + if not ep_queue_item.started and ep_queue_item.success is None: + return json.dumps({'result': 'success'}) #I Actually want to call it queued, because the search hasnt been started yet! + if ep_queue_item.started and ep_queue_item.success is None: + return json.dumps({'result': 'success'}) + else: + return json.dumps({'result': 'failure'}) - return json.dumps({'result': statusStrings[ep_obj.status], - 'quality': quality_class - }) + ### Returns the current ep_queue_item status for the current viewed show. + # Possible status: Downloaded, Snatched, etc... + # Returns {'show': 279530, 'episodes' : ['episode' : 6, 'season' : 1, 'searchstatus' : 'queued', 'status' : 'running', 'quality': '4013'] + def getManualSearchStatus(self, show=None, season=None): - return json.dumps({'result': 'failure'}) + episodes = [] + currentManualSearchThreadsQueued = [] + currentManualSearchThreadActive = [] + finishedManualSearchThreadItems= [] + + # Queued Searches + currentManualSearchThreadsQueued = sickbeard.searchQueueScheduler.action.get_all_ep_from_queue(show) + # Running Searches + if (sickbeard.searchQueueScheduler.action.is_manualsearch_in_progress()): + currentManualSearchThreadActive = sickbeard.searchQueueScheduler.action.currentItem + + # Finished Searches + finishedManualSearchThreadItems = sickbeard.search_queue.MANUAL_SEARCH_HISTORY + + if currentManualSearchThreadsQueued: + for searchThread in currentManualSearchThreadsQueued: + searchstatus = 'queued' + + episodes.append({'episode': searchThread.segment.episode, + 'episodeindexid': searchThread.segment.indexerid, + 'season' : searchThread.segment.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[searchThread.segment.status], + 'quality': self.getQualityClass(searchThread.segment)}) + + if currentManualSearchThreadActive: + searchThread = currentManualSearchThreadActive + searchstatus = 'searching' + if searchThread.success: + searchstatus = 'finished' + episodes.append({'episode': searchThread.segment.episode, + 'episodeindexid': searchThread.segment.indexerid, + 'season' : searchThread.segment.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[searchThread.segment.status], + 'quality': self.getQualityClass(searchThread.segment)}) + + if finishedManualSearchThreadItems: + for searchThread in finishedManualSearchThreadItems: + if str(searchThread.show.indexerid) == show and not [x for x in episodes if x['episodeindexid'] == searchThread.segment.indexerid]: + searchstatus = 'finished' + episodes.append({'episode': searchThread.segment.episode, + 'episodeindexid': searchThread.segment.indexerid, + 'season' : searchThread.segment.season, + 'searchstatus' : searchstatus, + 'status' : statusStrings[searchThread.segment.status], + 'quality': self.getQualityClass(searchThread.segment)}) + + return json.dumps({'show': show, 'episodes' : episodes}) + #return json.dumps() + + def getQualityClass(self, ep_obj): + # return the correct json value + + # Find the quality class for the episode + quality_class = Quality.qualityStrings[Quality.UNKNOWN] + ep_status, ep_quality = Quality.splitCompositeStatus(ep_obj.status) + for x in (SD, HD720p, HD1080p): + if ep_quality in Quality.splitQuality(x)[0]: + quality_class = qualityPresetStrings[x] + break + + return quality_class def searchEpisodeSubtitles(self, show=None, season=None, episode=None):