Browse Source

Change free up some screen real estate on manage/Bulk Change.

Change rotate column headers on manage/Bulk Change for supported browsers to reduce screen estate waste.
Add column to manage/Bulk Change to display the show folder location size.
Add media stats to manage/Bulk Change and view-show when hovering over folder size.
Add to manage/Bulk Change an icon where show location no longer exists and group the icon/non icon shows.
Add a hover tip to the edit column on manage/Bulk Change to remind about using multi-select.
Change add tooltips on manage/Bulk Change checkbox actions to display what each are used for.
Add to manage/Bulk Change confirm dialog before removing or deleting a show.
Change manage/Bulk Change add sort by size options to table, (Total, Largest, Smallest, Average).
Change manage/Bulk Change add busy spinner for processing when changing size sort type.
Change manage/Bulk Change table make header stick when page is scrolled.
Change manage/Bulk Change table make footer stick when page is scrolled.
Change manage/Bulk Change add filter to table, showname, quality, and status.
Change number of shows listed after a filter is displayed at the bottom of Bulk Change.
Change edit and submit buttons are disabled when there is no selection on Bulk Change.
Change edit and submit buttons display number of selected items on Bulk Change.
Change tidy up html markup and JavaScript for manage/Bulk Change.
Change refactor to simplify bulk_change logic.
tags/release_0.25.1
JackDandy 4 years ago
parent
commit
3f69a7fddc
  1. 18
      CHANGES.md
  2. 28
      gui/slick/css/dark.css
  3. 27
      gui/slick/css/light.css
  4. 4
      gui/slick/css/style.css
  5. 2
      gui/slick/interfaces/default/displayShow.tmpl
  6. 237
      gui/slick/interfaces/default/manage.tmpl
  7. 263
      gui/slick/js/bulkChange.js
  8. 50
      gui/slick/js/inc_top.js
  9. 22
      sickbeard/helpers.py
  10. 6
      sickbeard/tv.py
  11. 206
      sickbeard/webserve.py

18
CHANGES.md

@ -1,5 +1,23 @@
### 0.24.0 (2021-xx-xx xx:xx:00 UTC)
* Change free up some screen real estate on manage/Bulk Change
* Change rotate column headers on manage/Bulk Change for supported browsers to reduce screen estate waste
* Add column to manage/Bulk Change to display the show folder location size
* Add media stats to manage/Bulk Change and view-show when hovering over folder size
* Add to manage/Bulk Change an icon where show location no longer exists and group the icon/non icon shows
* Add a hover tip to the edit column on manage/Bulk Change to remind about using multi-select
* Change add tooltips on manage/Bulk Change checkbox actions to display what each are used for
* Add to manage/Bulk Change confirm dialog before removing or deleting a show
* Change manage/Bulk Change add sort by size options to table, (Total, Largest, Smallest, Average)
* Change manage/Bulk Change add busy spinner for processing when changing size sort type
* Change manage/Bulk Change table make header stick when page is scrolled
* Change manage/Bulk Change table make footer stick when page is scrolled
* Change manage/Bulk Change add filter to table, showname, quality, and status
* Change number of shows listed after a filter is displayed at the bottom of Bulk Change
* Change edit and submit buttons are disabled when there is no selection on Bulk Change
* Change edit and submit buttons display number of selected items on Bulk Change
* Change tidy up html markup and JavaScript for manage/Bulk Change
* Change refactor to simplify bulk_change logic
* Add to config/General, "Package updates" and list packages, check packages by default on Windows, others must enable
* Change simplify section config/General/Updates
* Add check for package updates to menu item action "Check for Updates"

28
gui/slick/css/dark.css

@ -1545,6 +1545,10 @@ thead.tablesorter-stickyHeader{
background-color:#2e2e2e
}
.sort-size-type.tablesorter-headerSorted{
background-color:#2c6590
}
.tablesorter tr.tablesorter-filter-row,
.tablesorter tr.tablesorter-filter-row td{
text-align:center;
@ -1552,11 +1556,22 @@ thead.tablesorter-stickyHeader{
border-bottom:1px solid #111
}
.tablesorter-filter-row input[type="search"]{
box-shadow:inset rgba(255, 255, 255, 0.2) 0 1px 0
}
#tfoot{
text-shadow:-1px -1px 0 rgba(0, 0, 0, 0.3)
}
#tfoot,
.tablesorter tfoot tr{
color:#ddd;
text-align:center;
text-shadow:-1px -1px 0 rgba(0, 0, 0, 0.3);
background-color:#333;
background-color:#333
}
.tablesorter tfoot tr{
border-collapse:collapse
}
@ -1564,6 +1579,15 @@ thead.tablesorter-stickyHeader{
color:#ddd
}
#bulk-change-table .tablesorter-filter-row,
#bulk-change-table .tablesorter-filter-row td,
#bulk-change-table-sticky .tablesorter-filter-row,
#bulk-change-table-sticky .tablesorter-filter-row td,
#tfoot{
background:#282828;
border-bottom:0
}
#showListTable tbody{
color:#000
}

27
gui/slick/css/light.css

@ -1470,6 +1470,10 @@ thead.tablesorter-stickyHeader{
background-color:#dfdacf
}
.sort-size-type.tablesorter-headerSorted{
background-color:#4a4a4a
}
.tablesorter tr.tablesorter-filter-row,
.tablesorter tr.tablesorter-filter-row td{
text-align:center;
@ -1477,11 +1481,23 @@ thead.tablesorter-stickyHeader{
border-bottom:1px solid #ddd
}
.tablesorter-filter-row input[type="search"]{
box-shadow:inset rgba(0, 0, 0, 0.2) 0 0 0 1px
}
#tfoot{
text-shadow:-1px -1px 0 rgba(255, 255, 255, 0.6)
}
#tfoot,
.tablesorter tfoot tr{
color:#ddd;
text-align:center;
background-color:#333
}
.tablesorter tfoot tr{
text-shadow:-1px -1px 0 rgba(0, 0, 0, 0.3);
background-color:#333;
border-collapse:collapse
}
@ -1489,6 +1505,15 @@ thead.tablesorter-stickyHeader{
color:#fff
}
#bulk-change-table .tablesorter-filter-row,
#bulk-change-table .tablesorter-filter-row td,
#bulk-change-table-sticky .tablesorter-filter-row,
#bulk-change-table-sticky .tablesorter-filter-row td,
#tfoot{
background:#d7d7d7;
border-bottom:0
}
/* =======================================================================
jquery.confirm.css
========================================================================== */

4
gui/slick/css/style.css

@ -4806,7 +4806,8 @@ tablesorter.css
}
.tablesorter th,
.tablesorter td{
.tablesorter td,
#tfoot{
padding:4px;
/*border-top:1px solid;*/
/*border-left:1px solid;*/
@ -4836,6 +4837,7 @@ tablesorter.css
vertical-align:middle
}
.tablesorter thead .sort-size-type.tablesorter-headerSorted,
#display-show .tablesorter .tablesorter-header,
#display-show .tablesorter thead .tablesorter-headerDesc,
#display-show .tablesorter thead .tablesorter-headerAsc{background-image:none}

2
gui/slick/interfaces/default/displayShow.tmpl

@ -333,7 +333,7 @@
<div id="details-bottom">
<span class="label addQTip" title="Info language, $show_obj.lang"><img src="$sbRoot/images/flags/${show_obj.lang}.png" width="16" height="11" alt="" style="margin-top:-1px" /></span>
<span class="label addQTip" title="Location#echo (' no longer exists" style="background-color:#8f1515"', '"')[$showLoc[1]]#>$showLoc[0]</span>
<span class="label addQTip" title="Size">$human($get_size($showLoc[0]))</span>
<span id="data-size" class="label" style="cursor:help">$human($get_size($showLoc[0]))</span>
#set $filecount = sum([$c for $k, $c in $ep_counts['videos'].items()])
#set $to_prune = $show_obj.prune - $filecount
#set $keep_or_prune = ('', ' (%s)' % ('%s to prune' % abs($to_prune), 'keep %s' % $show_obj.prune)[0 <= $to_prune])[bool($show_obj.prune)]

237
gui/slick/interfaces/default/manage.tmpl

@ -1,25 +1,23 @@
#import sickbeard
#from sickbeard.common import *
#from sickbeard.helpers import get_size, human
##
#set global $title = 'Bulk Change'
#set global $header = 'Bulk Change'
#set global $sbPath = '../..'
#set global $topmenu = 'manage'
<% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp#
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp#
##
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
##
#set $has_any_sports = False
#set $has_any_anime = False
#set $has_any_flat_folders = False
#set $show_list = sorted($sickbeard.showList, key=lambda _x: _x.name)
#for $cur_show_obj in $show_list
#set $has_any_sports |= bool($cur_show_obj.sports)
#set $has_any_anime |= bool($cur_show_obj.anime)
#set $has_any_flat_folders |= $bool(cur_show_obj.flatten_folders)
#end for
<script type="text/javascript" charset="utf-8">
<!--
## an arbitrary high number greater than a media file size
#set $high = 1000000000000
\$.SickGear.high = $high;
\$.tablesorter.addParser({
id: 'showNames',
is: function(s) { return !1; },
@ -38,40 +36,56 @@
type: 'numeric'
});
##
#set $columns_total = 15 - ((1, 0)[$has_any_sports] + (1, 0)[$has_any_anime] + (1, 0)[$has_any_flat_folders] + (1, 0)[$sickbeard.USE_SUBTITLES])
#set $column_headers = [('false', False), ("'showNames'", False), ("'quality'", False),
((None, "'sports'")[$has_any_sports], True),
("'scene'", True), ((None, "'anime'")[$has_any_anime], True),
((None, "'flatfold'")[$has_any_flat_folders], True), ("'paused'", True),
("'status'", False), ('false', False), ('false', False), ('false', False),
((None, 'false')[$sickbeard.USE_SUBTITLES], False), ('false', False), ('false', False)]
#set $enable_tvinfo = False
#set $column_headers = [
('!1', '!1', False),
("'showNames'", '!0', False),
("'size'", '!1', False), ('!1', '!1', False),
((None, "'tvinfo'")[$enable_tvinfo], '!1', False),
("'quality'", '!0', False),
((None, "'sports'")[$has_any_sports], '!1', True),
("'scene'", '!1', True),
((None, "'anime'")[$has_any_anime], '!1', True),
((None, "'flatfold'")[$has_any_flat_folders], '!1', True),
("'paused'", '!1', True), ("'status'", '!0', False),
('!1', '!1', False), ('!1', '!1', False), ('!1', '!1', False), ((None, '!1')[$sickbeard.USE_SUBTITLES], '!1', False), ('!1', '!1', False), ('!1', '!1', False)
]
#set $columns_total = $len($column_headers) - ((1, 0)[$has_any_sports] + (1, 0)[$has_any_anime] + (1, 0)[$has_any_flat_folders] + (1, 0)[$sickbeard.USE_SUBTITLES])
#set $headers = []
#set $text_extract = []
#set $column = -1
#for $k, ($c, $img_extract) in enumerate($column_headers)
#if None is $c
#for $sort, $filter, $img_extract in $column_headers
#if None is $sort
#continue
#end if
#set $column += 1
#set void = $headers.append('\t\t\t%s: { sorter: %s }' % ($column, $c))
#set void = $headers.append('\t\t\t%s: {sorter:%s, filter:%s}' % ($column, $sort, $filter))
#if $img_extract
#set void = $text_extract.append('\t\t\t%s%s' % ($column, ": function(node) {return $(node).find('img').attr('alt')}"))
#end if
#end for
\$(document).ready(function()
\$(function()
{
\$('#bulkChangeTable:has(tbody tr)').tablesorter({
widgets: ['zebra'],
\$('#bulk-change-table:has(tbody tr)').tablesorter({
widgets: ['zebra','stickyHeaders', 'filter'],
sortList: [[1,0]],
headers: {
#echo ',\n'.join($headers)#
},
textExtraction: {
2: function(node) {return \$(node).find('span').text().toLowerCase()},
#raw
1: function(node) {return ((!!$(node).find('i.img-warning-16').length ? '1_' : '0_') + $(node).text().toLowerCase())},
2: function(node) {return parseInt($(node).closest('[data-tvid_prodid]').attr('data-size') || -100, 10)},
#end raw
#if $enable_tvinfo
4: function(node) {return \$(node).find('i').attr('data-tvid')},
#end if
#echo ('5','4')[not $enable_tvinfo]#: function(node) {return \$(node).find('span').text().toLowerCase()},
#echo ',\n'.join($text_extract)#
}
});
});
//-->
</script>
@ -81,129 +95,174 @@
#else
<h1 class="title">$title</h1>
#end if
<style>
.tvShow{text-align:left}
.tvShow i{margin-right:6px; margin-bottom:1px}
.rotate-body span{float:left}
@supports (transform:translateX(16px) rotate(-45deg)) and (transform-origin:bottom left){
.tablesorter.rotate th.tablesorter-header.sort-icon-left{background-position:center left; padding: 4px 4px 4px 18px}
.tablesorter.rotate th.tablesorter-header.sort-icon-bottom{background-position:10px 54px /*28px 45px*/}
.tablesorter.rotate th.tablesorter-header.sort-icon-status{background-position:30px 54px}
.tablesorter.rotate th.tablesorter-header.sort-size{background-position:26px 54px; padding:4px 14px 4px 14px}
.tablesorter.rotate th.tablesorter-header.sort-icon-quality{background-position:20px 54px}
.rotate-holder{width:20px; margin-bottom:-16px}
.rotate-holder2{width:20px; margin:22px 0 -22px}
.rotate-body{width:25px; transform-origin:bottom left; transform:translateX(16px) rotate(-45deg)}
.rotate-body2{width:25px; transform-origin:bottom left; transform:translateX(20px) rotate(-45deg)}
.rotate-body span{float:none}
.rotate th.col-legend, .rotate td.col-legend{width:40px}
.rotate input[type="checkbox"].right-2px{margin:0 2px 0 0}
.rotate .img-warning-16{cursor:help}
}
#bulk-change-table thead,
#bulk-change-table-sticky thead,
#tfoot{position:sticky}
#bulk-change-table thead,
#bulk-change-table-sticky thead{top:50px}
#bulk-change-table-sticky,.tablesorter thead,.tablesorter thead tr,.tablesorter thead tr th, .tablesorter thead tr td{border-spacing:0}
#tfoot{bottom:0; min-height:34px; line-height:26px}
#tfoot input[disabled]{cursor:not-allowed}
.tablesorter-sticky-wrapper{margin-top:50px}
.tablesorter-filter-row input[disabled]{display:none}
.tablesorter-filter-row input[type="search"]{background:rgba(255,255,255,0.2); padding:0 3px; border:0; border-radius:4px; color:inherit}
.tablesorter .tablesorter-header.sort-size-type{padding:0; width:16px; cursor:pointer}
th.sort-size-type{font-weight:normal}
.average{border-top:1px solid white; width:9px; margin:0 auto}
.average i{display:block; line-height:10px; margin-right:1px}
#set $theme_suffix = ('', '-dark')['dark' == $sg_str('THEME_NAME', 'dark')]
.tip,.tip-average{margin:0 2px}
.tip-average{border-top:1px solid #echo ('#999', '#666')[not $theme_suffix]#}
.tip-average,.tip-average i{display:inline-block; line-height:8px}
.bfr{position:absolute;left:-999px;top:-999px}.bfr img,.spinner{display:inline-block;width:16px;height:16px}.spinner{background:url(${sbRoot}/images/loading16${theme_suffix}.gif) no-repeat 0 0}
</style>
<div class="bfr"><img src="$sbRoot/images/loading16${theme_suffix}.gif"></div>
<form name="bulkChangeForm" method="post" action="bulk_change">
$xsrf_form_html
<table id="bulkChangeTable" class="sickbeardTable tablesorter" cellspacing="1" border="0" cellpadding="0">
<table id="bulk-change-table" class="sickbeardTable tablesorter rotate" cellspacing="1" border="0" cellpadding="0">
<thead>
<tr>
<th class="col-checkbox">Edit<br /><input type="checkbox" class="bulkCheck" id="editCheck"></th>
<th class="nowrap narrow" style="text-align:left">Show Name</th>
<th class="col-legend narrow">Quality</th>
<tr style="height:70px"> <!-- must inline this css -->
<th class="col-checkbox"><div>Edit</div><input type="checkbox" class="bulk-check" id="edit-check"></th>
<th class="text-nowrap narrow sort-icon-left" style="text-align:left">Show Name</th>
<th class="sort-size col-legend narrow dir-none"><div class="rotate-holder"><div class="rotate-body">Size</div></div></th>
<th class="narrow sort-size-type" data-type="E" title="total"><div class="sort-size-type-body">&Sigma;</div><span class="sort-size-type-image" style="display:none; margin-top:8px"><i class="spinner"></i></span></th>
#if $enable_tvinfo
<th class="col-legend narrow sort-icon-bottom"><div class="rotate-holder"><div class="rotate-body">TV Info</div></div></th>
#end if
<th class="col-legend narrow sort-icon-quality"><div class="rotate-holder"><div class="rotate-body">Quality</div></div></th>
#if $has_any_sports
<th class="col-legend narrow">Sports</th>
<th class="col-legend narrow sort-icon-bottom"><div class="rotate-holder"><div class="rotate-body">Sports</div></div></th>
#end if
<th class="col-legend narrow">Scene</th>
<th class="col-legend narrow sort-icon-bottom"><div class="rotate-holder"><div class="rotate-body2">Scene</div></div></th>
#if $has_any_anime
<th class="col-legend narrow">Anime</th>
<th class="col-legend narrow sort-icon-bottom"><div class="rotate-holder"><div class="rotate-body2">Anime</div></div></th>
#end if
#if $has_any_flat_folders
<th class="col-legend narrow">Flat<br /> Folders</th>
<th class="col-legend narrow sort-icon-bottom"><div class="rotate-holder"><div class="rotate-body2">Flat Folders</div></div></th>
#end if
<th class="col-legend narrow">Paused</th>
<th class="col-legend narrow">Status</th>
<th width="1%">Update<br /><input type="checkbox" class="bulkCheck" id="updateCheck"></th>
<th width="1%">Rescan<br /><input type="checkbox" class="bulkCheck" id="refreshCheck"></th>
<th width="1%">Rename<br /><input type="checkbox" class="bulkCheck" id="renameCheck"></th>
<th class="col-legend narrow sort-icon-bottom"><div class="rotate-holder"><div class="rotate-body2">Paused</div></div></th>
<th class="col-legend narrow sort-icon-status"><div class="rotate-holder"><div class="rotate-body2">Status</div></div></th>
<th width="1%"><div class="rotate-holder2"><div class="rotate-body"><label><input type="checkbox" class="bulk-check right-2px" id="update-check"><span>Update</span></label></div></div></th>
<th width="1%"><div class="rotate-holder2"><div class="rotate-body"><label><input type="checkbox" class="bulk-check right-2px" id="refresh-check"><span>Rescan</span></label></div></div></th>
<th width="1%"><div class="rotate-holder2"><div class="rotate-body"><label><input type="checkbox" class="bulk-check right-2px" id="rename-check"><span>Rename</span></label></div></div></th>
#if $sickbeard.USE_SUBTITLES
<th width="1%">Search<br />Subtitle<br /><input type="checkbox" class="bulkCheck" id="subtitleCheck"></th>
<th width="1%"><div class="rotate-holder2"><div class="rotate-body"><label><input type="checkbox" class="bulk-check right-2px" id="subtitleCheck"><span>Subtitle</span></label></div></div></th>
#end if
## <!-- <th>Force Metadata Regen <input type="checkbox" class="bulkCheck" id="metadataCheck"></th>//-->
<th width="1%">Delete<br /><input type="checkbox" class="bulkCheck" id="deleteCheck"></th>
<th width="1%">Remove<br /><input type="checkbox" class="bulkCheck" id="removeCheck"></th>
<th width="1%"><div class="rotate-holder2"><div class="rotate-body"><label><input type="checkbox" class="bulk-check right-2px" id="delete-check"><span>Delete</span></label></div></div></th>
<th width="1%"><div class="rotate-holder2" style="margin-right:40px;"><div class="rotate-body"><label><input type="checkbox" class="bulk-check right-2px" id="remove-check"><span>Remove</span></label></div></div></th>
</tr>
</thead>
<tfoot>
<tr>
<td rowspan="1" colspan="2" class="align-center alt"><input class="btn pull-left" type="button" value="Edit Selected" id="submitMassEdit"></td>
<td rowspan="1" colspan="#echo $columns_total-2#" class="align-right alt"><input class="btn pull-right" type="button" value="Submit" id="submitBulkChange"></td>
</tr>
</tfoot>
<tbody>
#set $disabled = ' disabled="disabled"'
#set $disabled_inprogress_tip = ' title="%s action is currently in progress for this show"'
#set $disabled_inprogress_tip = ' action is currently in progress for this show'
#set $disabled_subtitles_tip = ' title="Use edit to enable subtitle search for this show"'
#set $no = 'no16.png" title="No" alt="No'
#set $yes = 'yes16.png" title="Yes" alt="Yes'
#for $cur_show_obj in $show_list
#set $option_state = '<input type="checkbox" class="%sCheck" id="%s-{0:s}"%s>'.format($cur_show_obj.tvid_prodid)
#set $max = $high + 100
#for $cur_show_obj in $shows + $shows_no_loc
#set $show_loc = $cur_show_obj.path
#set $show_size = $max if not $show_loc else $get_size($show_loc)
#set $option_state = '<input type="checkbox"%s class="%s-check"%s>'
##
#set $curUpdate_disabled = $sickbeard.show_queue_scheduler.action.isBeingUpdated($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInUpdateQueue($cur_show_obj)
#set $reason = $disabled_inprogress_tip % 'Update'
#set $curUpdate = '%s>%s' % (('', $reason)[$curUpdate_disabled],
$option_state % ('update', 'update', ('', $disabled + $reason)[$curUpdate_disabled]))
#set $tip = ' title="Update%s"' % ('', $disabled_inprogress_tip)[$curUpdate_disabled]
#set $curUpdate = ($tip, $option_state % (('', $disabled)[$curUpdate_disabled], 'update', $tip))
##
#set $curRefresh_disabled = $sickbeard.show_queue_scheduler.action.isBeingRefreshed($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInRefreshQueue($cur_show_obj)
#set $reason = $disabled_inprogress_tip % 'Rescan'
#set $curRefresh = '%s>%s' % (('', $reason)[$curRefresh_disabled],
$option_state % ('refresh', 'refresh', ('', $disabled + $reason)[$curRefresh_disabled]))
#set $tip = ' title="Rescan%s"' % ('', $disabled_inprogress_tip)[$curRefresh_disabled]
#set $curRefresh = ($tip, $option_state % (('', $disabled)[$curRefresh_disabled], 'refresh', $tip))
##
#set $curRename_disabled = $sickbeard.show_queue_scheduler.action.isBeingRenamed($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInRenameQueue($cur_show_obj)
#set $reason = $disabled_inprogress_tip % 'Rename'
#set $curRename = '%s>%s' % (('', $reason)[$curRename_disabled],
$option_state % ('rename', 'rename', ('', $disabled + $reason)[$curRename_disabled]))
#set $tip = ' title="Rename%s"' % ('', $disabled_inprogress_tip)[$curRename_disabled]
#set $curRename = ($tip, $option_state % (('', $disabled)[$curRename_disabled], 'rename', $tip))
##
#set $subtitles_disabled = not $cur_show_obj.subtitles\
or $sickbeard.show_queue_scheduler.action.isBeingSubtitled($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInSubtitleQueue($cur_show_obj)
#set $reason = ($disabled_inprogress_tip % 'Search subtitle', $disabled_subtitles_tip)[not $cur_show_obj.subtitles]
#set $curSubtitle = '%s>%s' % (('', $reason)[$subtitles_disabled],
$option_state % ('subtitle', 'subtitle', ('', $disabled + $reason)[$subtitles_disabled]))
#set $tip = (' title="Search subtitle"', (' title="Search subtitle%s"' % $disabled_inprogress_tip,
$disabled_subtitles_tip)[not $cur_show_obj.subtitles])[$subtitles_disabled]
#set $curSubtitle = ($tip, $option_state % (('', $disabled)[$subtitles_disabled], 'subtitle', $tip))
##
#set $curDelete_disabled = $sickbeard.show_queue_scheduler.action.isBeingRenamed($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInRenameQueue($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInRefreshQueue($cur_show_obj)
#set $reason = $disabled_inprogress_tip % 'Rename or rescan'
#set $curDelete = '%s>%s' % (('', $reason)[$curDelete_disabled],
$option_state % ('delete', 'delete', ('', $disabled + $reason)[$curDelete_disabled]))
#set $tip = ' title="Delete%s"' % ('', $disabled_inprogress_tip)[$curDelete_disabled]
#set $curDelete = ($tip, $option_state % (('', $disabled)[$curDelete_disabled], 'delete', $tip))
##
#set $curRemove_disabled = $sickbeard.show_queue_scheduler.action.isBeingRenamed($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInRenameQueue($cur_show_obj)\
or $sickbeard.show_queue_scheduler.action.isInRefreshQueue($cur_show_obj)
##set $reason = $disabled_inprogress_tip % 'Rename or rescan'
#set $curRemove = '%s>%s' % (('', $reason)[$curRemove_disabled],
$option_state % ('remove', 'remove', ('', $disabled + $reason)[$curRemove_disabled]))
<tr>
<td align="center"><input type="checkbox" class="editCheck" id="edit-$cur_show_obj.tvid_prodid"></td>
#set $tip = ' title="Remove%s"' % ('', $disabled_inprogress_tip)[$curRemove_disabled]
#set $curRemove = ($tip, $option_state % (('', $disabled)[$curRemove_disabled], 'remove', $tip))
<tr data-tvid_prodid="$cur_show_obj.tvid_prodid" data-size="$show_size">
<td><input type="checkbox" class="edit-check"></td>
#set $display_name = (re.sub(r'^((?:A(?!\s+to)n?)|The)\s(\w)', r'<span class="article">\1</span> \2', $cur_show_obj.name), $cur_show_obj.name)[$sickbeard.SORT_ARTICLE]
<td class="tvShow"><a href="$sbRoot/home/view-show?tvid_prodid=${cur_show_obj.tvid_prodid}">$display_name</a></td>
<td class="tvShow">#if not $show_loc#<i class="img-warning-16" title="Location no longer exists"></i>#end if#<a href="$sbRoot/home/view-show?tvid_prodid=${cur_show_obj.tvid_prodid}">$display_name</a></td>
<td colspan=2>#if 0 <= $show_size < $max#<span class="text-nowrap ui-size">$human($show_size)</span>#end if#</td>
#if $enable_tvinfo
<td><i class="icon16 $sickbeard.TVInfoAPI($cur_show_obj.tvid).config['slug']" data-tvid="$cur_show_obj.tvid"></i></td>
#end if
#if $cur_show_obj.quality in $qualityPresets
<td align="center"><span class="quality $qualityPresetStrings[$cur_show_obj.quality]">$qualityPresetStrings[$cur_show_obj.quality]</span></td>
<td><span class="quality $qualityPresetStrings[$cur_show_obj.quality]">$qualityPresetStrings[$cur_show_obj.quality]</span></td>
#else
<td align="center"><span class="quality Custom">Custom</span></td>
<td><span class="quality Custom">Custom</span></td>
#end if
#if $has_any_sports
<td align="center"><img src="$sbRoot/images/#if 1 == int($cur_show_obj.is_sports) then $yes else $no#" width="16" height="16" /></td>
<td><img src="$sbRoot/images/#if 1 == int($cur_show_obj.is_sports) then $yes else $no#" width="16" height="16"></td>
#end if
<td align="center"><img src="$sbRoot/images/#if 1 == int($cur_show_obj.is_scene) then $yes else $no#" width="16" height="16" /></td>
<td><img src="$sbRoot/images/#if 1 == int($cur_show_obj.is_scene) then $yes else $no#" width="16" height="16"></td>
#if $has_any_anime
<td align="center"><img src="$sbRoot/images/#if 1 == int($cur_show_obj.is_anime) then $yes else $no#" width="16" height="16" /></td>
<td><img src="$sbRoot/images/#if 1 == int($cur_show_obj.is_anime) then $yes else $no#" width="16" height="16"></td>
#end if
#if $has_any_flat_folders
<td align="center"><img src="$sbRoot/images/#if 1 == int($cur_show_obj.flatten_folders) then $yes else $no#" width="16" height="16" /></td>
<td><img src="$sbRoot/images/#if 1 == int($cur_show_obj.flatten_folders) then $yes else $no#" width="16" height="16"></td>
#end if
<td align="center"><img src="$sbRoot/images/#if 1 == int($cur_show_obj.paused) then $yes else $no#" width="16" height="16" /></td>
<td align="center">$cur_show_obj.status</td>
<td align="center"$curUpdate</td>
<td align="center"$curRefresh</td>
<td align="center"$curRename</td>
<td><img src="$sbRoot/images/#if 1 == int($cur_show_obj.paused) then $yes else $no#" width="16" height="16"></td>
<td>$cur_show_obj.status</td>
<td$curUpdate[0]>$curUpdate[1]</td>
<td$curRefresh[0]>$curRefresh[1]</td>
<td$curRename[0]>$curRename[1]</td>
#if $sickbeard.USE_SUBTITLES
<td align="center"$curSubtitle</td>
<td$curSubtitle[0]>$curSubtitle[1]</td>
#end if
<td align="center"$curDelete</td>
<td align="center"$curRemove</td>
<td$curDelete[0]>$curDelete[1]</td>
<td$curRemove[0]><span style="margin-right: 40px">$curRemove[1]</span></td>
</tr>
#end for
</tbody>
</table>
<div id="tfoot">
<span style="width:20%" class="pull-left"><input class="btn pull-left" type="button" value="Edit Selected" id="bulk-change-edit"></span>
<span class="stats grey-text"></span>
<span style="width:20%" class="pull-right"><input class="btn pull-right" type="button" value="Submit" id="bulk-change-submit"></span>
</div>
</form>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

263
gui/slick/js/bulkChange.js

@ -1,112 +1,223 @@
/** @namespace $.SickGear.Root */
$(document).ready(function() {
$(function(){
$('#submitMassEdit').click(function() {
var editArr = [];
$('#bulk-change-edit').click(function(){
var toDo = [];
$('.editCheck').each(function() {
if (true == this.checked) {
editArr.push($(this).attr('id').split('-')[1])
}
$('.edit-check:checked').each(function(){
toDo.push($(this).closest('tr').attr('data-tvid_prodid'));
});
if (0 == editArr.length)
if (0 === toDo.length)
return !1;
window.location.href = $.SickGear.Root + '/manage/mass-edit?to_edit=' + editArr.join('|');
window.location.href = $.SickGear.Root + '/manage/mass-edit?to_edit=' + toDo.join('|');
});
$('#bulk-change-submit').click(function(){
$('#submitBulkChange').click(function() {
var updateArr = [], refreshArr = [], renameArr = [], subtitleArr = [],
deleteArr = [], removeArr = [], metadataArr = [];
$('.updateCheck').each(function() {
if (true == this.checked) {
updateArr.push($(this).attr('id').split('-')[1])
}
});
$('.refreshCheck').each(function() {
if (true == this.checked) {
refreshArr.push($(this).attr('id').split('-')[1])
}
});
$('.renameCheck').each(function() {
if (true == this.checked) {
renameArr.push($(this).attr('id').split('-')[1])
}
});
$('.subtitleCheck').each(function() {
if (true == this.checked) {
subtitleArr.push($(this).attr('id').split('-')[1])
}
});
$('.deleteCheck').each(function() {
if (true == this.checked) {
deleteArr.push($(this).attr('id').split('-')[1])
}
});
var toChange = !1, toDo = {update: [], refresh: [], rename: [], subtitle: [], delete: [], remove: []};
$('.removeCheck').each(function() {
if (true == this.checked) {
removeArr.push($(this).attr('id').split('-')[1])
}
$.each(Object.keys(toDo), function(i, k){
$('.' + k + '-check:checked').each(function(){
toDo[k].push($(this).closest('tr').attr('data-tvid_prodid'));
toChange = !0;
});
});
/*
$('.metadataCheck').each(function() {
if (true == this.checked) {
metadataArr.push($(this).attr('id').split('-')[1])
}
});
*/
if (0 == updateArr.length + refreshArr.length + renameArr.length + subtitleArr.length + deleteArr.length + removeArr.length + metadataArr.length)
if (!toChange)
return !1;
window.location.href = $.SickGear.Root + '/manage/bulk-change?to_update=' + updateArr.join('|') + '&to_refresh=' + refreshArr.join('|') + '&to_rename=' + renameArr.join('|') + '&to_delete=' + deleteArr.join('|') + '&to_remove=' + removeArr.join('|') + '&to_metadata=' + metadataArr.join('|') + '&to_subtitle=' + subtitleArr.join('|');
var confirmArr = [],
command = function(){
window.location.href = $.SickGear.Root + '/manage/bulk-change?'
+ 'to_update=' + toDo.update.join('|')
+ '&to_refresh=' + toDo.refresh.join('|')
+ '&to_rename=' + toDo.rename.join('|')
+ '&to_subtitle=' + toDo.subtitle.join('|')
+ '&to_delete=' + toDo.delete.join('|')
+ '&to_remove=' + toDo.remove.join('|');
return !0;
};
if(toDo.delete.length){
confirmArr.push('delete ' + toDo.delete.length);
}
if(toDo.remove.length){
confirmArr.push('remove ' + toDo.remove.length);
}
if(!confirmArr.length){
command();
} else {
$.confirm({
'title': 'Are you sure ?',
'message': confirmArr.join(' and ') + ' show(s)',
'buttons': {
'Yes': {
'class': 'green',
'action': command
},
'No': {
'class': 'red',
'action': function(){
} // No op. This action property can be omitted.
}
}
});
}
});
$('.bulkCheck').click(function() {
var bulkCheck = this, whichBulkCheck = $(bulkCheck).attr('id');
$('.' + whichBulkCheck).each(function() {
var updateButtons = function(){
var editBtn$ = $('#bulk-change-edit'), submitBtn$ = $('#bulk-change-submit'), numEdits = $('.edit-check:checked').length,
numToDo = $('.update-check:checked, .refresh-check:checked, .rename-check:checked, .delete-check:checked, .remove-check:checked').length;
if(!!numEdits){
editBtn$.attr('disabled', !1);
editBtn$.val('Edit ' + numEdits + ' selected');
} else {
editBtn$.attr('disabled', !0);
editBtn$.val('Select items');
}
if(!!numToDo){
submitBtn$.attr('disabled', !1);
submitBtn$.val('Submit ' + numToDo + ' action(s)');
} else {
submitBtn$.attr('disabled', !0);
submitBtn$.val('Select actions');
}
}
updateButtons();
$('.bulk-check').click(function(){
$('.' + $(this).attr('id')).each(function(){
if (!this.disabled)
this.checked = !this.checked
});
updateButtons();
});
['.editCheck', '.updateCheck', '.refreshCheck', '.renameCheck', '.deleteCheck', '.removeCheck'].forEach(function(name) {
['.edit-check', '.update-check', '.refresh-check', '.rename-check', '.delete-check', '.remove-check'].forEach(function(name){
var lastCheck = null;
$(name).click(function(event) {
$(name).click(function(event){
if(!lastCheck || !event.shiftKey){
if(!lastCheck || !event.shiftKey) {
lastCheck = this;
return;
} else {
var check = this, found = 0;
$(name).each(function(){
switch (found){
case 2:
return !1;
case 1:
if (!this.disabled)
this.checked = lastCheck.checked;
}
if (this === check || this === lastCheck)
found++;
});
}
updateButtons();
});
});
var check = this, found = 0;
$('#edit-check, .edit-check').each(function(i, el$){
$(el$).parent().attr('title', 'multiselect = ctrl/shift + left click');
})
$.SickGear.onRefreshSize = !1;
function presortTable(table$){
var asc = $('.sort-size.tablesorter-headerAsc').length,
desc = $('.sort-size.tablesorter-headerDesc').length,
value;
if (!!(asc + desc) || !!$.SickGear.onRefreshSize){
$('[data-size]').each(function(i, el){
value = parseInt($(el).attr('data-size'), 10);
$(el).attr('data-size', (!!desc !== !!$.SickGear.onRefreshSize) /* xor */
? ((value < 0) ? (-1 * value) + $.SickGear.high : value)
: ((value > $.SickGear.high) ? -1 * (value - $.SickGear.high) : value))
});
table$.trigger('updateCache');
}
}
$('#bulk-change-table')
.bind('refreshComplete', function(){
$.SickGear.onRefreshSize = !0;
})
.bind('sortEnd', function(){
$.SickGear.onRefreshSize = !1;
var el$ = $('.sort-size-type');
if (!!($('.sort-size.tablesorter-headerAsc').length + $('.sort-size.tablesorter-headerDesc').length)){
el$.removeClass('tablesorter-headerUnSorted').addClass('tablesorter-headerSorted');
} else {
el$.removeClass('tablesorter-headerSorted');
}
})
.bind('sortStart', function(e, t){
presortTable($(t));
})
.bind('filterInit filterEnd', function(e, data){
$('#tfoot').find('.stats').html(
(data.filteredRows === data.totalRows
? '<span class="total-rows">' + data.totalRows + '</span> shows'
: '<span class="filter-rows">' + data.filteredRows + '</span> of <span class="total-rows">' + data.totalRows + '</span> shows (' + (data.totalRows - data.filteredRows) + ' filtered)'));
});
$(name).each(function() {
switch (found) {
case 2:
return !1;
case 1:
if (!this.disabled)
this.checked = lastCheck.checked;
$('.sort-size-type').on('click', function(){
$('.sort-size-type-body').hide();
$('.sort-size-type-image').show();
var that = $(this), title = 'total', htmlType = '&Sigma;', dataType = 'E', key = 'Size';
if(dataType === $(this).attr('data-type')){
title = 'average';
htmlType = '<div class="average"><i>x</i></div>';
dataType = 'x';
key = 'AverageSize';
} else if('x' === $(this).attr('data-type')){
title = 'largest';
htmlType = '&gt';
dataType = '>';
key = 'Largest';
} else if('>' === $(this).attr('data-type')){
title = 'smallest';
htmlType = '&lt';
dataType = '<';
key = 'Smallest';
}
var url = $.SickGear.Root + '/home/media_stats';
$.getJSON(url).then(function(content){
// on success...
var html, val, el$;
$.each(content, function(tvidProdid, data){
html = '---';
val = '-10';
el$ = $('tr[data-tvid_prodid="' + tvidProdid + '"][data-size]');
if (/undefined/.test(data.message) || 'E' === dataType){
html = data['h' + key];
val = data['b' + key];
}
if (this == check || this == lastCheck)
found++;
el$.find('.ui-size').html(html);
el$.attr('data-size', val);
});
that.attr('title', title);
that.attr('data-type', dataType);
that.find('.sort-size-type-body').html(htmlType);
$('#bulk-change-table').trigger('updateAll', [!0]);
$('.sort-size-type-image').hide();
$('.sort-size-type-body').show();
}, function(xhr, status, error){
// on failure...
console.log('data fetch error', url, status + ': ' + error);
$('.sort-size-type-image').hide();
$('.sort-size-type-body').show();
});
});

50
gui/slick/js/inc_top.js

@ -1,3 +1,11 @@
/** @namespace $.SickGear.Root */
/** @namespace content.path */
/** @namespace content.hSize */
/** @namespace content.nFiles */
/** @namespace content.hLargest */
/** @namespace content.hSmallest */
/** @namespace content.hAverageSize */
function initActions() {
var menu$ = $('#SubMenu');
menu$.find('a[href*="/home/restart/"]').addClass('btn restart').html('<i class="sgicon-restart"></i>Restart');
@ -99,4 +107,46 @@ $(function(){
});
});
})
$('[data-size] .ui-size, #data-size').each(function(){
$(this).qtip({
content:{
text: function(event, api){
// deferred object ensuring the request is only made once
var tvidProdid = $('#tvid-prodid').val(), tipText = '';
if (/undefined/i.test(tvidProdid)){
tvidProdid = $(event.currentTarget).closest('[data-tvid_prodid]').attr('data-tvid_prodid');
}
$.getJSON($.SickGear.Root + '/home/media_stats', {tvid_prodid: tvidProdid})
.then(function(content){
// on success...
if (/undefined/.test(content[tvidProdid].message)){
tipText = (1 === content[tvidProdid].nFiles
? '<span class="grey-text">One media file,</span> ' + content[tvidProdid].hAverageSize
: '' + content[tvidProdid].nFiles + ' <span class="grey-text">media files,</span>'
+ ((content[tvidProdid].hLargest === content[tvidProdid].hSmallest)
? (content[tvidProdid].hAverageSize + ' <span class="grey-text">each</span>')
: ('<br><span class="grey-text">largest</span> ' + content[tvidProdid].hLargest + ' <span class="grey-text">(<span class="tip">></span>)</span>'
+ '<br><span class="grey-text">smallest</span> ' + content[tvidProdid].hSmallest + ' <span class="grey-text">(<span class="tip"><</span>)</span>'
+ '<br><span class="grey-text">average size</span> ' + content[tvidProdid].hAverageSize + ' <span class="grey-text">(<span class="tip-average"><i>x</i></span>)</span>')));
} else {
tipText = '<span class="grey-text">' + content[tvidProdid].message + '</span>';
}
api.set('content.text',
tipText + '<div style="width:100%; border-top:1px dotted; margin-top:3px"></div>'
+ '<div style="margin-top:3px">' + '<span class="grey-text">location size</span> ' + content[tvidProdid].hSize + ' <span class="grey-text">(<span class="tip">&Sigma;</span>)</span>' + '</div>'
+ content[tvidProdid].path);
}, function(xhr, status, error){
// on fail...
api.set('content.text', status + ': ' + error);
});
return 'Loading...'; // set initial text
}
},
show: {solo: true},
position: {viewport: $(window), my: 'left center', adjust: {y: -10, x: 0}},
style: {classes: 'qtip-dark qtip-rounded qtip-shadow'}
});
});
});

22
sickbeard/helpers.py

@ -1168,6 +1168,28 @@ def get_size(start_path='.'):
return 0
def get_media_stats(start_path='.'):
# type: (AnyStr) -> Tuple[int, int, int, int]
"""
return recognised media stats for a folder as...
number of media files, smallest size in bytes, largest size in bytes, average size in bytes
:param start_path: path to scan
"""
if ek.ek(os.path.isdir, start_path):
sizes = sorted(map(lambda y: y.stat(follow_symlinks=False).st_size,
filter(lambda x: has_media_ext(x.name), scantree(start_path))))
if sizes:
return len(sizes), sizes[0], sizes[-1], int(sum(sizes) / len(sizes))
elif ek.ek(os.path.isfile, start_path):
size = ek.ek(os.path.getsize, start_path)
return 1, size, size, size
return 0, 0, 0, 0
def remove_article(text=''):
"""
remove articles from text

6
sickbeard/tv.py

@ -206,6 +206,9 @@ class TVidProdid(object):
class TVShow(TVShowBase):
__slots__ = (
'path',
)
def __init__(self, tvid, prodid, lang=''):
# type: (int, int, Text) -> None
@ -219,7 +222,7 @@ class TVShow(TVShowBase):
self._not_found_count = None # type: None or int
self._last_found_on_indexer = -1 # type: int
self._location = '' # type: AnyStr
self._location = self.path = '' # type: AnyStr
# self._is_location_good = None
self.lock = threading.Lock()
self.sxe_ep_obj = {} # type: Dict
@ -422,6 +425,7 @@ class TVShow(TVShowBase):
# Don't validate dir if user wants to add shows without creating a dir
if sickbeard.ADD_SHOWS_WO_DIR or ek.ek(os.path.isdir, new_location):
self.dirty_setter('_location')(self, new_location)
self.path = new_location
# self._is_location_good = True
else:
raise exceptions_helper.NoNFOException('Invalid folder for the show!')

206
sickbeard/webserve.py

@ -54,7 +54,7 @@ from .anime import AniGroupList, pull_anidb_groups, short_group_names
from .browser import folders_at_path
from .common import ARCHIVED, DOWNLOADED, FAILED, IGNORED, SKIPPED, SNATCHED, SNATCHED_ANY, UNAIRED, UNKNOWN, WANTED, \
SD, HD720p, HD1080p, UHD2160p, Overview, Quality, qualityPresetStrings, statusStrings
from .helpers import has_image_ext, real_path, remove_article, remove_file_perm, starify
from .helpers import get_media_stats, has_image_ext, real_path, remove_article, remove_file_perm, starify
from .indexermapper import MapStatus, map_indexers_to_show, save_mapping
from .indexers.indexer_config import TVINFO_IMDB, TVINFO_TRAKT, TVINFO_TVDB
from .name_parser.parser import InvalidNameException, InvalidShowException, NameParser
@ -2301,6 +2301,32 @@ class Home(MainHandler):
return 'Episode not found.' if not sql_result else (sql_result[0]['description'] or '')[:250:]
@staticmethod
def media_stats(tvid_prodid=None):
if None is tvid_prodid:
shows = sickbeard.showList
else:
shows = [helpers.find_show_by_id(tvid_prodid)]
response = {}
for cur_show_obj in shows:
if cur_show_obj.path:
loc_size = helpers.get_size(cur_show_obj.path)
num_files, smallest, largest, average_size = get_media_stats(cur_show_obj.path)
response[cur_show_obj.tvid_prodid] = {'message': 'No media files'} if not num_files else \
{
'nFiles': num_files,
'bSmallest': smallest, 'hSmallest': helpers.human(smallest),
'bLargest': largest, 'hLargest': helpers.human(largest),
'bAverageSize': average_size, 'hAverageSize': helpers.human(average_size)
}
response[cur_show_obj.tvid_prodid].update({
'path': cur_show_obj.path, 'bSize': loc_size, 'hSize': helpers.human(loc_size)})
return json.dumps(response)
@staticmethod
def scene_exceptions(tvid_prodid, wanted_season=None):
exceptionsList = sickbeard.scene_exceptions.get_all_scene_exceptions(tvid_prodid)
@ -5354,6 +5380,21 @@ class Manage(MainHandler):
def index(self):
t = PageTemplate(web_handler=self, file='manage.tmpl')
t.submenu = self.manage_menu('Bulk')
t.has_any_sports = False
t.has_any_anime = False
t.has_any_flat_folders = False
t.shows = []
t.shows_no_loc = []
for cur_show_obj in sorted(sickbeard.showList, key=lambda _x: _x.name.lower()):
t.has_any_sports |= bool(cur_show_obj.sports)
t.has_any_anime |= bool(cur_show_obj.anime)
t.has_any_flat_folders |= bool(cur_show_obj.flatten_folders)
if not cur_show_obj.path:
t.shows_no_loc += [cur_show_obj]
else:
t.shows += [cur_show_obj]
return t.respond()
def get_status_episodes(self, tvid_prodid, which_status):
@ -6058,126 +6099,55 @@ class Manage(MainHandler):
self.redirect('/manage/')
def bulk_change(self, to_update=None, to_refresh=None,
to_rename=None, to_delete=None, to_remove=None,
to_metadata=None, to_subtitle=None):
if None is not to_update:
to_update = to_update.split('|')
else:
to_update = []
if None is not to_refresh:
to_refresh = to_refresh.split('|')
else:
to_refresh = []
if None is not to_rename:
to_rename = to_rename.split('|')
else:
to_rename = []
if None is not to_delete:
to_delete = to_delete.split('|')
else:
to_delete = []
if None is not to_remove:
to_remove = to_remove.split('|')
else:
to_remove = []
if None is not to_metadata:
to_metadata = to_metadata.split('|')
else:
to_metadata = []
if None is not to_subtitle:
to_subtitle = to_subtitle.split('|')
else:
to_subtitle = []
errors = []
updates = []
refreshes = []
renames = []
subs = []
def bulk_change(self, to_update='', to_refresh='', to_rename='',
to_subtitle='', to_delete='', to_remove='', **kwargs):
for cur_tvid_prodid in set(to_update + to_refresh
+ to_rename + to_delete + to_remove
+ to_metadata + to_subtitle):
to_change = dict({_tvid_prodid: helpers.find_show_by_id(_tvid_prodid)
for _tvid_prodid in
next(iter([_x.split('|') for _x in (to_update, to_refresh, to_rename, to_subtitle,
to_delete, to_remove) if _x]), '')})
if '' == cur_tvid_prodid:
continue
show_obj = helpers.find_show_by_id(cur_tvid_prodid)
if None is show_obj:
continue
update, refresh, rename, subtitle, errors = [], [], [], [], []
for cur_tvid_prodid, cur_show_obj in iteritems(to_change):
if cur_tvid_prodid in to_delete:
show_obj.delete_show(True)
# don't do anything else if it's being deleted
continue
if cur_tvid_prodid in to_remove:
show_obj.delete_show()
# don't do anything else if it's being remove
continue
if cur_tvid_prodid in to_update:
try:
sickbeard.show_queue_scheduler.action.updateShow(show_obj, True, True)
updates.append(show_obj.name)
except exceptions_helper.CantUpdateException as e:
errors.append('Unable to update show ' + show_obj.name + ': ' + ex(e))
cur_show_obj.delete_show(True)
# don't bother refreshing shows that were updated anyway
if cur_tvid_prodid in to_refresh and cur_tvid_prodid not in to_update:
try:
sickbeard.show_queue_scheduler.action.refreshShow(show_obj)
refreshes.append(show_obj.name)
except exceptions_helper.CantRefreshException as e:
errors.append('Unable to refresh show ' + show_obj.name + ': ' + ex(e))
elif cur_tvid_prodid in to_remove:
cur_show_obj.delete_show()
if cur_tvid_prodid in to_rename:
sickbeard.show_queue_scheduler.action.renameShowEpisodes(show_obj)
renames.append(show_obj.name)
if sickbeard.USE_SUBTITLES and cur_tvid_prodid in to_subtitle:
sickbeard.show_queue_scheduler.action.download_subtitles(show_obj)
subs.append(show_obj.name)
if 0 < len(errors):
ui.notifications.error('Errors encountered',
'<br >\n'.join(errors))
messageDetail = ''
if 0 < len(updates):
messageDetail += '<br /><b>Updates</b><br /><ul><li>'
messageDetail += '</li><li>'.join(updates)
messageDetail += '</li></ul>'
else:
if cur_tvid_prodid in to_update:
try:
sickbeard.show_queue_scheduler.action.updateShow(cur_show_obj, True, True)
update.append(cur_show_obj.name)
except exceptions_helper.CantUpdateException as e:
errors.append('Unable to update show %s: %s' % (cur_show_obj.name, ex(e)))
if 0 < len(refreshes):
messageDetail += '<br /><b>Refreshes</b><br /><ul><li>'
messageDetail += '</li><li>'.join(refreshes)
messageDetail += '</li></ul>'
elif cur_tvid_prodid in to_refresh:
try:
sickbeard.show_queue_scheduler.action.refreshShow(cur_show_obj)
refresh.append(cur_show_obj.name)
except exceptions_helper.CantRefreshException as e:
errors.append('Unable to refresh show %s: %s' % (cur_show_obj.name, ex(e)))
if 0 < len(renames):
messageDetail += '<br /><b>Renames</b><br /><ul><li>'
messageDetail += '</li><li>'.join(renames)
messageDetail += '</li></ul>'
if cur_tvid_prodid in to_rename:
sickbeard.show_queue_scheduler.action.renameShowEpisodes(cur_show_obj)
rename.append(cur_show_obj.name)
if 0 < len(subs):
messageDetail += '<br /><b>Subtitles</b><br /><ul><li>'
messageDetail += '</li><li>'.join(subs)
messageDetail += '</li></ul>'
if sickbeard.USE_SUBTITLES and cur_tvid_prodid in to_subtitle:
sickbeard.show_queue_scheduler.action.download_subtitles(cur_show_obj)
subtitle.append(cur_show_obj.name)
if 0 < len(updates + refreshes + renames + subs):
ui.notifications.message('The following actions were queued:',
messageDetail)
if len(errors):
ui.notifications.error('Errors encountered', '<br>\n'.join(errors))
if len(update + refresh + rename + subtitle):
ui.notifications.message(
'Queued the following actions:',
''.join(['%s:<br>* %s<br>' % (_to_do, '<br>'.join(_shows))
for (_to_do, _shows) in (('Updates', update), ('Refreshes', refresh),
('Renames', rename), ('Subtitles', subtitle)) if len(_shows)]))
self.redirect('/manage/')
def failed_downloads(self, limit=100, to_remove=None):
@ -7341,7 +7311,7 @@ class ConfigGeneral(Config):
for v in results:
logger.log(v, logger.ERROR)
ui.notifications.error('Error(s) Saving Configuration',
'<br />\n'.join(results))
'<br>\n'.join(results))
else:
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
@ -7511,7 +7481,7 @@ class ConfigSearch(Config):
for x in results:
logger.log(x, logger.ERROR)
ui.notifications.error('Error(s) Saving Configuration',
'<br />\n'.join(results))
'<br>\n'.join(results))
else:
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
@ -7638,7 +7608,7 @@ class ConfigMediaProcess(Config):
for x in results:
logger.log(x, logger.ERROR)
ui.notifications.error('Error(s) Saving Configuration',
'<br />\n'.join(results))
'<br>\n'.join(results))
else:
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
@ -8051,7 +8021,7 @@ class ConfigProviders(Config):
if 0 < len(results):
for x in results:
logger.log(x, logger.ERROR)
ui.notifications.error('Error(s) Saving Configuration', '<br />\n'.join(results))
ui.notifications.error('Error(s) Saving Configuration', '<br>\n'.join(results))
else:
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
@ -8319,7 +8289,7 @@ class ConfigNotifications(Config):
for x in results:
logger.log(x, logger.ERROR)
ui.notifications.error('Error(s) Saving Configuration',
'<br />\n'.join(results))
'<br>\n'.join(results))
else:
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
@ -8374,7 +8344,7 @@ class ConfigSubtitles(Config):
for x in results:
logger.log(x, logger.ERROR)
ui.notifications.error('Error(s) Saving Configuration',
'<br />\n'.join(results))
'<br>\n'.join(results))
else:
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
@ -8407,7 +8377,7 @@ class ConfigAnime(Config):
for x in results:
logger.log(x, logger.ERROR)
ui.notifications.error('Error(s) Saving Configuration',
'<br />\n'.join(results))
'<br>\n'.join(results))
else:
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
@ -8524,7 +8494,7 @@ class EventLogs(MainHandler):
final_data += ['<code><span class="prelight">'] + \
['<span class="prelight-num">%02s)</span> %s' % (n + 1, x)
for n, x in enumerate(normal_data[::-1])] + \
['</span></code><br />']
['</span></code><br>']
num_lines += len(normal_data)
normal_data = []

Loading…
Cancel
Save