Compare commits

...

20 Commits

Author SHA1 Message Date
Travis
5d5b1bf053 Automatic translation update 2017-07-24 08:16:18 +00:00
Safihre
ea4cdba3eb Update text files for 2.2.0Beta2 2017-07-24 10:08:21 +02:00
Safihre
d6ecebc75a Use existing texts for Tag duplicate 2017-07-24 10:07:15 +02:00
Jonathon Saine
b0af6a1761 User requested a way to track dupes but not have sab block/discard/pause the items (as he doesnt want to have to manually unpause). Figured we could add a 'Tag' switch for the Dupe detection. Works same way as Pause (shows duplicate tag in queue/adds warning/internally tags it) but does not pause the item.
Once the file is done downloading and makes it to the queue, you have no idea it was a duplicate.
You would have to use the 'Warning' and search for that.. and then assume the newer one was the dupe... we really should expose in the History that an item was a duplicate. Right now the 'search' box only looks at the name.. and I dont think we should be messing with the name to add duplicate there. I'd rather us just add a tag/flag whatever to notate this.. and can filter/sort on it. We also should do the same to the Queue as well.. since changing the name of the item to be `DUPLICATE / XXXXX` is a little jarring.

Now if we just exposed the dupe flag in the history, then the whole 'tag' option really isnt needed as it was just a means to an end.
2017-07-24 10:00:15 +02:00
Safihre
6e350f30fc Add midnight auto history-purge and modify texts 2017-07-24 09:47:05 +02:00
Safihre
169137c631 Implement History Retention setting
Closes #678
2017-07-24 09:47:05 +02:00
Safihre
6393dc0dca Remove history_limit from Specials 2017-07-24 09:47:05 +02:00
Safihre
1a27b4824b Style improvements to Queue and Server Graphs
Closes #977
2017-07-24 09:36:48 +02:00
Safihre
2b59a383cf Change label in History-status to 'Direct Unpack' 2017-07-24 09:05:02 +02:00
Safihre
efbaaade22 Correct server graph timezone effects and only show months that we have 2017-07-23 23:14:10 +02:00
Safihre
cf7e7b1f62 Only show usage data for days that have passed 2017-07-23 23:14:10 +02:00
Safihre
2c1746a92d Show Montly usage graphs per server in Config 2017-07-23 23:14:10 +02:00
Travis
a2e57fd3d8 Automatic translation update 2017-07-22 17:27:02 +00:00
Safihre
932f8d9176 Wizard access was not limited by login and external access rules
Bad bad 
Closes #972
2017-07-22 18:54:33 +02:00
Safihre
5ffd82da89 Show vote up/down instead of Video/Audio score 2017-07-22 00:19:40 +02:00
Safihre
8b3de191d9 Add Retry All Failed button to Glitter 2017-07-22 00:11:13 +02:00
Travis
83d8a23e2c Automatic translation update 2017-07-20 22:13:49 +00:00
Safihre
58b107a4b5 Allow up to 5 missing/CRC'ed errors before cancelling Direct Unpack
Sometimes a CRC error is not so bad it turns out
2017-07-20 23:55:36 +02:00
Safihre
a40609b39d Direct Unpack was started before whole file was written to disk
In case part 2 came in before part 1.
2017-07-20 23:55:36 +02:00
Sander Jo
0faa5d3dff Print applied permissions in octal 2017-07-20 21:04:00 +02:00
27 changed files with 813 additions and 207 deletions

View File

@@ -1,7 +1,7 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 2.2.0Beta1
Summary: SABnzbd-2.2.0Beta1
Version: 2.2.0Beta2
Summary: SABnzbd-2.2.0Beta2
Home-page: https://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org

View File

@@ -1,4 +1,4 @@
Release Notes - SABnzbd 2.2.0 Beta 1
Release Notes - SABnzbd 2.2.0 Beta 2
=========================================================
NOTE: Due to changes in this release, the queue will be converted when 2.2.0
@@ -6,6 +6,17 @@ is started for the first time. Job order, settings and data will be
preserved, but all jobs will be unpaused and URL's that did not finish
fetching before the upgrade will be lost!
## Bugfixes and changes since Beta 1
- Graphical overview of daily server usage on Servers page
- New option History Retention to limit number of jobs in History
- Add Retry All Failed button to Glitter
- Add option to only tag a duplicate job without pausing or removing it
- Remove video and audio rating icons from Queue
- Show vote buttons instead of video and audio rating buttons in History
- Direct Unpack could crash
- Wizard was always accessible, even with username and password set
- Several styling fixes in the interface
## Bugfixes since Alpha 3
- Bugfixes and stability updates for Direct Unpack
- Notification errors

View File

@@ -32,7 +32,9 @@
<link rel="apple-touch-icon" sizes="192x192" href="${root}staticcfg/ico/android-192x192.png" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/chartist.min.css" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?p=$pid" />
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=$version" />
<script type="text/javascript">

View File

@@ -110,6 +110,56 @@
</div><!-- /section -->
</form>
<script type="text/javascript" src="${root}staticcfg/js/chartist.min.js"></script>
<script type="text/javascript">
// Define variables needed for the server-plots
var serverData = {}
var chartOptions = {
fullWidth: true,
showPoint: false,
axisX: {
labelOffset: {
x: -5
},
showGrid: false
},
axisY: {
labelOffset: {
y: 7
},
scaleMinSpace: 30
},
chartPadding: {
top: 9,
bottom: 0,
left: 30,
right: 20
}
}
</script>
<!--
We need to find how many months we have recorded so far, so we
loop over all the dates to find the lowest value and then use
the number of days passed as an estimate of the months we have.
-->
<!--#import json#-->
<!--#import datetime#-->
<!--#def show_date_selector($server, $id)#-->
<!--#set month_names = [$T('January'), $T('February'), $T('March'), $T('April'), $T('May'), $T('June'), $T('July'), $T('August'), $T('September'), $T('October'), $T('November'), $T('December')] #-->
<!--#set min_date = datetime.date.today()#-->
<!--#for date in $server['amounts'][4]#-->
<!--#set split_date = $date.split('-')#-->
<!--#set min_date = min(min_date, datetime.date(int(split_date[0]), int(split_date[1]), int(split_date[2])))#-->
<!--#end for#-->
<!--#set months_recorded = int((datetime.date.today()-min_date).days / (365/12))#-->
<select class="chart-selector" name="chart-selector-${id}" id="chart-selector-${id}" data-id="${id}">
<!--#for $i in range(months_recorded+1)#-->
<!--#set cur_date = (datetime.date.today() - datetime.timedelta($i*365/12))#-->
<option value="<!--#echo '%d-%02d' % ($cur_date.year, $cur_date.month)#-->">$month_names[$cur_date.month-1] $cur_date.year</option>
<!--#end for#-->
</select>
<!--#end def#-->
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
<!--#set $cur_prio_color = -1 #-->
<!--#set $last_prio = -1 #-->
@@ -232,166 +282,282 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
<div class="col2" style="display:block;">
<!--#if 'amounts' in $server#-->
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $(server['amounts'][0])B<br/>
$T('today'): $(server['amounts'][3])B<br/>
$T('thisWeek'): $(server['amounts'][2])B<br/>
$T('thisMonth'): $(server['amounts'][1])B
<!--#end if#-->
</div>
</div><!-- /section -->
<div class="col1" style="display:block;">
<!--#if 'amounts' in $server#-->
<div class="server-amounts-text">
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $(server['amounts'][0])B<br/>
$T('today'): $(server['amounts'][3])B<br/>
$T('thisWeek'): $(server['amounts'][2])B<br/>
$T('thisMonth'): $(server['amounts'][1])B
</div>
<div class="server-chart">
$show_date_selector($server, $cur)
<div id="server-chart-${cur}" class="ct-chart"></div>
</div>
<script type="text/javascript">
// Server data
serverData[${cur}] = <!--#echo json.dumps($server['amounts'][4])#-->
\$(document).ready(function() {
showChart(${cur})
})
</script>
<!--#end if#-->
</div>
</div>
</form>
<!--#end for#-->
</div><!-- /colmask -->
<script type="text/javascript">
\$(document).ready(function(){
// Exception when change of priority, reload
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
\$('.fullform').submit(function() {
// Skip the fancy stuff, just submit
this.submit()
})
})
function showChart(server_id, month) {
// This month
var thisDay = new Date()
/**
Message on no Default category selected
**/
function checkServerCats() {
// Now we check all of them
var hasDefault = false;
// Only check the active servers, not the add-server one
\$('.section:not(#addServerContent) select[name="categories"]').each(function() {
// See if this server is enabled
if(!\$(this).parents('.section').find('.col2').hasClass('server-disabled') ) {
// Is there Default?
if(\$(this).val() && \$(this).val().indexOf('Default') > -1) {
// Hide
\$('.alert-no-category').hide()
hasDefault = true
// All good!
return true
}
// What month are we doing?
if(month) {
var inputDate = new Date(month+'-01')
} else {
var inputDate = new Date()
}
var baseDate = new Date(inputDate.getFullYear(), inputDate.getMonth(), 1)
var maxDaysInMonth = new Date(baseDate.getYear(), baseDate.getMonth()+1, 0).getDate()
// Fill the data array
var data = {
labels: [],
series: [[]]
};
var largestVal = 0
for(var i = 1; i < maxDaysInMonth+1; i++) {
// Add X-label
if(i % 3 == 1) {
data['labels'].push(i)
} else {
data['labels'].push(NaN)
}
// Get formatted date
baseDate.setDate(i)
var dateCheck = toFormattedDate(baseDate)
// Add data if we have it
if(dateCheck in serverData[server_id]) {
data['series'][0].push(serverData[server_id][dateCheck])
largestVal = Math.max(largestVal, serverData[server_id][dateCheck])
} else if(thisDay.getYear() == baseDate.getYear() && thisDay.getMonth() == baseDate.getMonth() && thisDay.getDate() < i) {
data['series'][0].push(NaN)
} else {
data['series'][0].push(0)
}
}
// Check if we should shrink the Y-axis values
var devideBy = 1024
var axisLabel = 'KB'
if(largestVal > 1024*1024) {
devideBy = 1024*1024
axisLabel = 'MB'
}
if(largestVal > 1024*1024*1024) {
devideBy = 1024*1024*1024
axisLabel = 'GB'
}
if(largestVal > 1024*1024*1024*1024) {
devideBy = 1024*1024*1024*1024
axisLabel = 'TB'
}
// Shrink the value
data['series'][0] = data['series'][0].map(function(num) {
return num / devideBy;
})
// We found nothing.. Let's show a warning
if(!hasDefault) \$('.alert-no-category').show()
// Show the chart
chart = new Chartist.Line('#server-chart-'+server_id, data, chartOptions);
chart.on('created', function(context) {
context.svg.elem('rect', {
x: context.chartRect.x1,
y: context.chartRect.y2,
width: context.chartRect.width(),
height: context.chartRect.height()+2,
fill: 'none',
stroke: '#B9B9B9',
'stroke-width': '1px'
})
\$('#server-chart-'+server_id+' .ct-label.ct-vertical').each(function(index, elmn) {
elmn.innerHTML += axisLabel
})
});
}
\$('select[name="categories"]').on('change', checkServerCats)
checkServerCats()
// Need to mitigate timezone effects!
function toFormattedDate(date) {
var local = new Date(date);
local.setMinutes(date.getMinutes() - date.getTimezoneOffset());
return local.toJSON().slice(0, 10);
}
/**
Click events
When finished loading
**/
\$('.showserver').click(function () {
if(\$(this).parent().hasClass('server-disabled')) {
\$(this).parent().parent().toggleClass('server-disabled')
}
\$(this).parent().next().toggle();
\$(this).parent().next().next().toggle();
if (\$(this).attr("value") == "$T('showDetails')") {
\$(this).attr("value", "$T('hideDetails')");
} else {
\$(this).attr("value", "$T('showDetails')");
}
});
\$(document).ready(function(){
// Exception when change of priority, reload
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
\$('.fullform').submit(function() {
// Skip the fancy stuff, just submit
this.submit()
})
})
\$('#addServerButton').click(function(){
\$('#addServer').hide();
\$('#addServerContent').show();
});
/**
Update charts when changed
**/
\$('.chart-selector').on('change', function(elemn) {
showChart(\$(elemn.target).data('id'), \$(elemn.target).val())
// Lets us leave (needs to be called after the change event)
setTimeout(function() {
formWasSubmitted = true;
formHasChanged = false;
}, 100)
})
\$('[name="ssl"]').click(function() {
// Use CSS transitions to do some highlighting
var portBox = \$(this).parent().parent().find('[name="port"]')
if(this.checked) {
// Enabled SSL change port when not already a custom port
if(portBox.val() == '119') {
portBox.val('563')
portBox.addClass('port-highlight')
/**
Message on no Default category selected
**/
function checkServerCats() {
// Now we check all of them
var hasDefault = false;
// Only check the active servers, not the add-server one
\$('.section:not(#addServerContent) select[name="categories"]').each(function() {
// See if this server is enabled
if(!\$(this).parents('.section').find('.col2').hasClass('server-disabled') ) {
// Is there Default?
if(\$(this).val() && \$(this).val().indexOf('Default') > -1) {
// Hide
\$('.alert-no-category').hide()
hasDefault = true
// All good!
return true
}
}
})
// We found nothing.. Let's show a warning
if(!hasDefault) \$('.alert-no-category').show()
}
\$('select[name="categories"]').on('change', checkServerCats)
checkServerCats()
/**
Click events
**/
\$('.showserver').click(function () {
if(\$(this).parent().hasClass('server-disabled')) {
\$(this).parent().parent().toggleClass('server-disabled')
}
} else {
// Remove SSL port
if(portBox.val() == '563') {
portBox.val('119')
portBox.addClass('port-highlight')
}
}
setTimeout(function() { portBox.removeClass('port-highlight') }, 2000)
})
\$('.testServer').click(function(event){
removeObfuscation()
var theButton = \$(this)
var resultBox = theButton.parents('.col1').find('.result-box .alert');
theButton.attr("disabled", "disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
\$.ajax({
type: "POST",
url: "../../tapi",
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
}).then(function(data) {
// Let's replace the link
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
msg = msg.replace('-', '<br>')
// Fill the box and enable the button
resultBox.removeClass('alert-success alert-danger').show()
resultBox.html(msg)
theButton.removeAttr("disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
// Succes or not?
if(data.value.result) {
resultBox.addClass('alert-success')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
\$(this).parent().next().toggle();
\$(this).parent().next().next().toggle();
if (\$(this).attr("value") == "$T('showDetails')") {
\$(this).attr("value", "$T('hideDetails')");
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
\$(this).attr("value", "$T('showDetails')");
}
});
});
\$('.delServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','delServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('#addServerButton').click(function(){
\$('#addServer').hide();
\$('#addServerContent').show();
});
\$('.clrServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','clrServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('[name="ssl"]').click(function() {
// Use CSS transitions to do some highlighting
var portBox = \$(this).parent().parent().find('[name="port"]')
if(this.checked) {
// Enabled SSL change port when not already a custom port
if(portBox.val() == '119') {
portBox.val('563')
portBox.addClass('port-highlight')
}
} else {
// Remove SSL port
if(portBox.val() == '563') {
portBox.val('119')
portBox.addClass('port-highlight')
}
}
setTimeout(function() { portBox.removeClass('port-highlight') }, 2000)
})
\$('.toggleServerCheckbox').click(function(){
var whichServer = \$(this).attr("name");
\$.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, session: "$session" }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 100)
\$('.testServer').click(function(event){
removeObfuscation()
var theButton = \$(this)
var resultBox = theButton.parents('.col1').find('.result-box .alert');
theButton.attr("disabled", "disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
\$.ajax({
type: "POST",
url: "../../tapi",
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
}).then(function(data) {
// Let's replace the link
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
msg = msg.replace('-', '<br>')
// Fill the box and enable the button
resultBox.removeClass('alert-success alert-danger').show()
resultBox.html(msg)
theButton.removeAttr("disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
// Succes or not?
if(data.value.result) {
resultBox.addClass('alert-success')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
}
});
});
\$('.delServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','delServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.clrServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','clrServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.toggleServerCheckbox').click(function(){
var whichServer = \$(this).attr("name");
\$.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, session: "$session" }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 100)
});
});
});
});
</script>
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->

View File

@@ -86,6 +86,7 @@
<label class="config" for="no_dupes">$T('opt-no_dupes')</label>
<select name="no_dupes" id="no_dupes">
<option value="0" <!--#if int($no_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
@@ -96,6 +97,7 @@
<label class="config" for="no_series_dupes">$T('opt-no_series_dupes')</label>
<select name="no_series_dupes" id="no_series_dupes">
<option value="0" <!--#if int($no_series_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_series_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_series_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_series_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_series_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
@@ -215,6 +217,20 @@
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list"/>
<span class="desc">$T('explain-cleanup_list')</span>
</div>
<div class="field-pair">
<label class="config" for="history_retention_select">$T('opt-history_retention')</label>
<input type="hidden" name="history_retention" id="history_retention" value="$history_retention">
<select name="history_retention_select" id="history_retention_select">
<option value="0">$T('history_retention-all')</option>
<option value="n">$T('history_retention-number')</option>
<option value="d">$T('history_retention-days')</option>
<option value="-1">$T('history_retention-none')</option>
</select>
<input type="number" id="history_retention_number" name="history_retention_number" min="1">
<span class="desc">$T('explain-history_retention').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
@@ -437,6 +453,53 @@
}
});
\$('#history_retention_select, #history_retention_number').on('change', updateHistoryRetention)
function updateHistoryRetention() {
var retention_setting = \$('#history_retention')
var retention_select = \$('#history_retention_select').val()
var retention_number = \$('#history_retention_number')
// Keep all or keep none
if(retention_select == "0" || retention_select == "-1") {
retention_number.hide()
retention_number.val('')
retention_number.attr('placeholder', '')
retention_setting.val(retention_select)
} else {
retention_number.show()
// Days or number?
if(retention_select.indexOf("d") !== -1) {
retention_number.attr('placeholder', '$T('days')')
if(retention_number.val()) {
retention_setting.val(retention_number.val() + 'd')
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
} else {
retention_number.attr('placeholder', '$T('history_retention-limit')')
if(retention_number.val()) {
retention_setting.val(retention_number.val())
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
}
}
}
// Set the history-retention settig
var retention_setting_value = \$('#history_retention').val()
if(parseInt(retention_setting_value) > 0) {
// Days or number?
if(retention_setting_value.indexOf("d") !== -1) {
\$('#history_retention_select').val("d")
} else {
\$('#history_retention_select').val("n")
}
\$('#history_retention_number').val(parseInt(retention_setting_value))
} else {
// Keep all or keep none
\$('#history_retention_select').val(retention_setting_value)
\$('#history_retention_number').hide()
}
\$('.restoreDefaults').click(function(e) {
// Get section name
var sectionName = \$(this).parents('.section').find('.col2 h3').text().trim()

View File

File diff suppressed because one or more lines are too long

View File

@@ -994,6 +994,46 @@ input[type="checkbox"] {
opacity: 0.7;
}
.Servers .server-amounts-text {
width: 20%;
float: left;
}
.Servers .server-chart {
float: right;
width: calc(100% - 250px - 25%);
text-align: center;
position: relative;
}
.Servers .ct-series-a .ct-line {
stroke: #666666;
}
.Servers .ct-label {
font-size: 1em;
color: black;
}
.Servers .chart-selector {
position: absolute;
display: block;
top: -7px;
left: 50%;
width: 120px;
margin-left: -40px;
min-width: initial;
opacity: 0.8;
}
.Servers .chart-selector:hover {
opacity: 1;
}
.Servers .ct-grid.ct-vertical:first-of-type {
display: none;
}
.advanced-settings {
display: none;
}
@@ -1171,6 +1211,15 @@ input[type="checkbox"] {
.Servers .col2 button:first-of-type {
margin-bottom: 0;
}
.Servers .server-chart {
display: none;
}
.Servers .server-amounts-text {
padding: 0px 15px 10px;
width: inherit;
}
}
@media screen and (max-width: 768px) {

View File

File diff suppressed because one or more lines are too long

View File

@@ -48,8 +48,8 @@
<!-- ko if: historyStatus.has_rating -->
<div class="dropdown history-ratings">
<a href="#" class="name-icons hover-button" data-toggle="dropdown" onclick="keepOpen(this)">
<span class="glyphicon glyphicon-facetime-video"></span> <span data-bind="text: historyStatus.rating_avg_video"></span>
<span class="glyphicon glyphicon-volume-up"></span> <span data-bind="text: historyStatus.rating_avg_audio"></span>
<span class="glyphicon glyphicon-thumbs-up"></span> <span data-bind="text: historyStatus.rating_avg_vote_up"></span>
<span class="glyphicon glyphicon-thumbs-down"></span> <span data-bind="text: historyStatus.rating_avg_vote_down"></span>
</a>
<ul class="dropdown-menu history-ratings-menu">
<li>
@@ -206,6 +206,7 @@
</ul>
<div class="multioperations-selector" id="history-options">
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="left" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('showAllHis') / $T('showFailedHis')" data-tooltip="true" data-placement="left" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-toggle="modal" data-tooltip="true" data-placement="left"><span class="glyphicon glyphicon-trash"></span></a>
</div>

View File

@@ -102,11 +102,11 @@
<form data-bind="submit: editingNameSubmit">
<input type="text" data-bind="value: nameForEdit, visible: editingName(), hasfocus: editingName" />
</form>
<div class="name-options" data-bind="visible: !editingName()">
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-MoveToTop')"><span class="glyphicon glyphicon-chevron-up"></span></a>
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-MoveToBottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
<a href="#" data-bind="click: editName, css: { disabled: isGrabbing() }" class="hover-button"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" data-bind="click: showFiles, css: { disabled: isGrabbing() }" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
<div class="name-options" data-bind="visible: !editingName(), css: { disabled: isGrabbing() }">
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')"><span class="glyphicon glyphicon-chevron-up"></span></a>
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
<a href="#" data-bind="click: editName" class="hover-button" title="$T('Glitter-rename')"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" data-bind="click: showFiles" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
<small data-bind="text: avg_age"></small>
</div>
</td>

View File

@@ -62,6 +62,7 @@
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/&quot;/g,'"');
glitterTranslate.removeDown = "$T('Glitter-confirmClearDownloads')";
glitterTranslate.removeDow1 = "$T('Glitter-confirmClear1Download')";
glitterTranslate.retryAll = "$T('link-retryAll')?";
glitterTranslate.encrypted = "$T('Glitter-encrypted')";
glitterTranslate.duplicate = "$T('Glitter-duplicate')";
glitterTranslate.tooLarge = "$T('Glitter-tooLarge')";

View File

@@ -169,7 +169,6 @@ function HistoryListModel(parent) {
// Toggle showing failed
self.toggleShowFailed = function(data, event) {
// Set the loader so it doesn't flicker and then switch
self.isLoading(true)
self.showFailed(!self.showFailed())
@@ -177,7 +176,20 @@ function HistoryListModel(parent) {
$('#history-options a').tooltip('hide')
// Force refresh
self.parent.refresh(true)
}
// Retry all failed
self.retryAllFailed = function(data, event) {
// Ask to be sure
if(confirm(glitterTranslate.retryAll)) {
// Send the command
callAPI({
mode: 'retry_all'
}).then(function() {
// Force refresh
self.parent.refresh(true)
})
}
}
// Empty history options

View File

@@ -533,9 +533,16 @@ tbody>tr>td:last-child {
}
.hover-button.disabled,
.hover-button.disabled:hover {
.hover-button.disabled:hover,
.name-options.disabled .hover-button,
.name-options.disabled .hover-button:hover {
cursor: not-allowed !important;
opacity: 0.1 !important;
pointer-events: none;
}
.name-options.disabled {
cursor: not-allowed !important;
}
.info-container {
@@ -656,12 +663,16 @@ td.name .name-icons {
text-decoration: none !important;
}
.queue-table td.name:hover .name-icons {
display: none;
}
td.name .name-icons .glyphicon {
margin-left: 2px;
top: 2px;
font-weight: bold;
}
.glyphicon-chevron-down,
.glyphicon-chevron-up {
top: 2px;
font-weight: bold;
}
tbody.no-downloads tr td {

View File

@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2017-06-22 20:42+0000\n"
"PO-Revision-Date: 2017-06-13 09:56+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"PO-Revision-Date: 2017-07-22 17:36+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>\n"
"Language-Team: Hebrew <he@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2017-06-23 05:55+0000\n"
"X-Generator: Launchpad (build 18416)\n"
"X-Launchpad-Export-Date: 2017-07-23 06:02+0000\n"
"X-Generator: Launchpad (build 18419)\n"
#: email/email.tmpl:1
msgid ""
@@ -64,6 +64,48 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## SABnzbd תבנית דוא\"ל ברירת מחדל עבור\n"
"## זאת תבנית ברדלס\n"
"## http://sabnzbd.wikidot.com/email-templates :תיעוד\n"
"##\n"
"## !שורות חדשות ורווחים לבנים הם משמעותיים\n"
"##\n"
"## אלו כותרות הדוא\"ל\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd has <!--#if $status then \"completed\" else \"failed\" #--> "
"job $name\n"
"## !אחרי זה בא הגוף, השורה הריקה דרושה\n"
"\n"
",היי\n"
"<!--#if $status #-->\n"
"SABnzbd הוריד את \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin "
"#\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd נכשל להוריד את \"$name\" <!--#if $msgid==\"\" then \"\" else "
"\"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"הסתיים ב $end_time\n"
"הורדו $size\n"
"\n"
":תוצאות העבודה\n"
"<!--#for $stage ב $stages #-->\n"
"שלב $stage <!--#slurp#-->\n"
"<!--#for $result ב $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
":(קוד יציאה = $script_ret) \"$script\" פלט מתסריט משתמש\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"!תהנה\n"
"<!--#else#-->\n"
"!סליחה\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
@@ -93,6 +135,29 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## SABnzbd עבור RSS תבנית דוא\"ל\n"
"## זאת תבנית ברדלס\n"
"## http://sabnzbd.wikidot.com/email-templates :תיעוד\n"
"##\n"
"## !שורות חדשות ורווחים לבנים הם משמעותיים\n"
"##\n"
"## אלו כותרות הדוא\"ל\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"הוסיף $amount עבודות לתור Subject: SABnzbd\n"
"## !אחרי זה בא הגוף, השורה הריקה דרושה\n"
"\n"
",היי\n"
"\n"
".הוסיף $amount עבודות לתור SABnzbd\n"
".\"$feed\" בשם RSS הם מהזנת\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"ביי\n"
#: email/badfetch.tmpl:1
msgid ""
@@ -119,3 +184,24 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd רעה עבור URL תבנית דוא\"ל של "
"משיכת\n"
"## זאת תבנית ברדלס\n"
"## http://sabnzbd.wikidot.com/email-templates :תיעוד\n"
"##\n"
"## !שורות חדשות ורווחים לבנים הם משמעותיים\n"
"##\n"
"## אלו כותרות הדוא\"ל\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"NZB נכשל במשיכת Subject: SABnzbd\n"
"## !אחרי זה בא הגוף, השורה הריקה דרושה\n"
"\n"
",היי\n"
"\n"
".$url מתוך NZB-נכשל לאחזר את ה SABnzbd\n"
"הודעת השגיאה הייתה: $msg\n"
"\n"
"ביי\n"

View File

@@ -12,7 +12,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 7bit\n"
"POT-Creation-Date: 2017-07-17 20:10+W. Europe Daylight Time\n"
"POT-Creation-Date: 2017-07-24 10:07+W. Europe Daylight Time\n"
"Generated-By: pygettext.py 1.5\n"
@@ -93,7 +93,7 @@ msgid "Error"
msgstr ""
#: SABnzbd.py # sabnzbd/interface.py # sabnzbd/interface.py
#: sabnzbd/osxmenu.py
#: sabnzbd/osxmenu.py # sabnzbd/wizard.py
msgid "SABnzbd shutdown finished"
msgstr ""
@@ -409,10 +409,14 @@ msgstr ""
msgid "%s => missing from all servers, discarding"
msgstr ""
#: sabnzbd/directunpacker.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py
msgid "Unpacking"
#: sabnzbd/directunpacker.py # sabnzbd/directunpacker.py
#: sabnzbd/directunpacker.py # sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""
#: sabnzbd/directunpacker.py # sabnzbd/skintext.py [PP status]
#: sabnzbd/skintext.py [History: job status]
msgid "Completed"
msgstr ""
#: sabnzbd/directunpacker.py # sabnzbd/newsunpack.py
@@ -779,6 +783,11 @@ msgstr ""
msgid "Unpacking failed, archive requires a password"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Unpacking"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/skintext.py [PP phase "unpack"]
msgid "Unpack"
msgstr ""
@@ -944,12 +953,12 @@ msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
msgid "Verifying"
msgstr ""
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
#: sabnzbd/skintext.py [PP status]
#: sabnzbd/newsunpack.py # sabnzbd/skintext.py [PP status]
msgid "Checking"
msgstr ""
@@ -1106,7 +1115,7 @@ msgstr ""
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbstuff.py
#: sabnzbd/nzbstuff.py # sabnzbd/nzbstuff.py [Warning message]
msgid "Duplicate NZB"
msgstr ""
@@ -1213,7 +1222,7 @@ msgid "Limit Speed"
msgstr ""
#: sabnzbd/osxmenu.py # sabnzbd/sabtray.py # sabnzbd/sabtraylinux.py
#: sabnzbd/skintext.py [#: Config->Scheduler] # sabnzbd/skintext.py [Pause downloading] # sabnzbd/skintext.py [Three way switch for duplicates]
#: sabnzbd/skintext.py [#: Config->Scheduler] # sabnzbd/skintext.py [Pause downloading] # sabnzbd/skintext.py [Four way switch for duplicates]
#: sabnzbd/skintext.py [Config->Scheduling]
msgid "Pause"
msgstr ""
@@ -1605,10 +1614,6 @@ msgstr ""
msgid "Failure"
msgstr ""
#: sabnzbd/skintext.py [PP status] # sabnzbd/skintext.py [History: job status]
msgid "Completed"
msgstr ""
#: sabnzbd/skintext.py [PP status] # sabnzbd/skintext.py
msgid "Failed"
msgstr ""
@@ -1817,6 +1822,54 @@ msgstr ""
msgid "Year"
msgstr ""
#: sabnzbd/skintext.py
msgid "January"
msgstr ""
#: sabnzbd/skintext.py
msgid "February"
msgstr ""
#: sabnzbd/skintext.py
msgid "March"
msgstr ""
#: sabnzbd/skintext.py
msgid "April"
msgstr ""
#: sabnzbd/skintext.py
msgid "May"
msgstr ""
#: sabnzbd/skintext.py
msgid "June"
msgstr ""
#: sabnzbd/skintext.py
msgid "July"
msgstr ""
#: sabnzbd/skintext.py
msgid "August"
msgstr ""
#: sabnzbd/skintext.py
msgid "September"
msgstr ""
#: sabnzbd/skintext.py
msgid "October"
msgstr ""
#: sabnzbd/skintext.py
msgid "November"
msgstr ""
#: sabnzbd/skintext.py
msgid "December"
msgstr ""
#: sabnzbd/skintext.py
msgid "Day of month"
msgstr ""
@@ -2595,6 +2648,34 @@ msgstr ""
msgid "List of file extensions that should be deleted after download.<br />For example: <b>nfo</b> or <b>nfo, sfv</b>"
msgstr ""
#: sabnzbd/skintext.py
msgid "History Retention"
msgstr ""
#: sabnzbd/skintext.py
msgid "Automatically delete completed jobs from History. Beware that Duplicate Detection and some external tools rely on History information."
msgstr ""
#: sabnzbd/skintext.py
msgid "Keep all jobs"
msgstr ""
#: sabnzbd/skintext.py
msgid "Keep maximum number of completed jobs"
msgstr ""
#: sabnzbd/skintext.py
msgid "Keep completed jobs maximum number of days"
msgstr ""
#: sabnzbd/skintext.py
msgid "Do not keep any completed jobs"
msgstr ""
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "History item limit"
msgstr ""
#: sabnzbd/skintext.py
msgid "Save Changes"
msgstr ""
@@ -2871,14 +2952,18 @@ msgstr ""
msgid "Detect identical episodes in series (based on \"name/season/episode\" of items in your History)"
msgstr ""
#: sabnzbd/skintext.py [Three way switch for duplicates]
#: sabnzbd/skintext.py [Four way switch for duplicates]
msgid "Discard"
msgstr ""
#: sabnzbd/skintext.py [Three way switch for duplicates]
#: sabnzbd/skintext.py [Four way switch for duplicates]
msgid "Fail job (move to History)"
msgstr ""
#: sabnzbd/skintext.py [Four way switch for duplicates]
msgid "Tag job"
msgstr ""
#: sabnzbd/skintext.py [Three way switch for encrypted posts]
msgid "Abort"
msgstr ""
@@ -2967,10 +3052,6 @@ msgstr ""
msgid "Automatically sort items by (average) age."
msgstr ""
#: sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""
#: sabnzbd/skintext.py
msgid "Posts will be paused untill they are at least this age. Setting job priority to Force will skip the delay."
msgstr ""
@@ -3983,10 +4064,6 @@ msgstr ""
msgid "Queue item limit"
msgstr ""
#: sabnzbd/skintext.py
msgid "History item limit"
msgstr ""
#: sabnzbd/skintext.py
msgid "Date format"
msgstr ""
@@ -4011,6 +4088,10 @@ msgstr ""
msgid "articles"
msgstr ""
#: sabnzbd/skintext.py
msgid "Rename"
msgstr ""
#: sabnzbd/skintext.py # sabnzbd/skintext.py
msgid "Queue repair"
msgstr ""

View File

@@ -906,7 +906,7 @@ def _api_server_stats(name, output, kwargs):
stats['servers'] = {}
for svr in config.get_servers():
t, m, w, d = BPSMeter.do.amounts(svr)
t, m, w, d, _ = BPSMeter.do.amounts(svr)
stats['servers'][svr] = {'total': t or 0, 'month': m or 0, 'week': w or 0, 'day': d or 0}
return report(output, keyword='', data=stats)

View File

@@ -118,6 +118,9 @@ class BPSMeter(object):
self.month_total = {}
self.grand_total = {}
self.timeline_total = {}
self.day_label = time.strftime("%Y-%m-%d")
self.end_of_day = tomorrow(t) # Time that current day will end
self.end_of_week = next_week(t) # Time that current day will end
self.end_of_month = next_month(t) # Time that current month will end
@@ -136,7 +139,7 @@ class BPSMeter(object):
data = (self.last_update, self.grand_total,
self.day_total, self.week_total, self.month_total,
self.end_of_day, self.end_of_week, self.end_of_month,
self.quota, self.left, self.q_time
self.quota, self.left, self.q_time, self.timeline_total
)
sabnzbd.save_admin(data, BYTES_FILE_NAME)
@@ -171,12 +174,15 @@ class BPSMeter(object):
self.last_update, self.grand_total, \
self.day_total, self.week_total, self.month_total, \
self.end_of_day, self.end_of_week, self.end_of_month = data[:8]
if len(data) == 11:
self.quota, self.left, self.q_time = data[8:]
if len(data) >= 11:
self.quota, self.left, self.q_time = data[8:11]
logging.debug('Read quota q=%s l=%s reset=%s',
self.quota, self.left, self.q_time)
if abs(quota - self.quota) > 0.5:
self.change_quota()
# Get timeline stats
if len(data) == 12:
self.timeline_total = data[11]
else:
self.quota = self.left = cfg.quota_size.get_float()
res = self.reset_quota()
@@ -199,6 +205,7 @@ class BPSMeter(object):
t = time.time()
if t > self.end_of_day:
# current day passed. get new end of day
self.day_label = time.strftime("%Y-%m-%d")
self.day_total = {}
self.end_of_day = tomorrow(t) - 1.0
@@ -227,6 +234,12 @@ class BPSMeter(object):
self.grand_total[server] = 0L
self.grand_total[server] += amount
if server not in self.timeline_total:
self.timeline_total[server] = {}
if self.day_label not in self.timeline_total[server]:
self.timeline_total[server][self.day_label]= 0L
self.timeline_total[server][self.day_label] += amount
# Quota check
if self.have_quota and self.quota_enabled:
self.left -= amount
@@ -290,7 +303,8 @@ class BPSMeter(object):
return self.grand_total.get(server, 0L), \
self.month_total.get(server, 0L), \
self.week_total.get(server, 0L), \
self.day_total.get(server, 0L)
self.day_total.get(server, 0L), \
self.timeline_total.get(server, {})
def clear_server(self, server):
""" Clean counters for specified server """
@@ -302,6 +316,8 @@ class BPSMeter(object):
del self.month_total[server]
if server in self.grand_total:
del self.grand_total[server]
if server in self.timeline_total:
del self.timeline_total[server]
self.save()
def get_bps(self):

View File

@@ -164,6 +164,7 @@ cleanup_list = OptionList('misc', 'cleanup_list')
unwanted_extensions = OptionList('misc', 'unwanted_extensions')
action_on_unwanted_extensions = OptionNumber('misc', 'action_on_unwanted_extensions', 0)
new_nzb_on_failure = OptionBool('misc', 'new_nzb_on_failure', False)
history_retention = OptionStr('misc', 'history_retention', '0')
quota_size = OptionStr('misc', 'quota_size')
quota_day = OptionStr('misc', 'quota_day')

View File

@@ -40,7 +40,7 @@ from sabnzbd.constants import DB_HISTORY_NAME, STAGES
from sabnzbd.encoding import unicoder
from sabnzbd.bpsmeter import this_week, this_month
from sabnzbd.decorators import synchronized
from sabnzbd.misc import get_all_passwords
from sabnzbd.misc import get_all_passwords, int_conv
DB_LOCK = threading.RLock()
@@ -249,6 +249,30 @@ class HistoryDB(object):
self.save()
def auto_history_purge(self):
""" Remove history items based on the configured history-retention """
if sabnzbd.cfg.history_retention() == "0":
return
if sabnzbd.cfg.history_retention() == "-1":
# Delete all non-failed ones
self.remove_completed()
if "d" in sabnzbd.cfg.history_retention():
# How many days to keep?
days_to_keep = int_conv(sabnzbd.cfg.history_retention().strip()[:-1])
seconds_to_keep = int(time.time()) - days_to_keep*3600*24
if days_to_keep > 0:
logging.info('Removing completed jobs older than %s days from history', days_to_keep)
return self.execute("""DELETE FROM history WHERE status = 'Completed' AND completed < ?""", (seconds_to_keep,), save=True)
else:
# How many to keep?
to_keep = int_conv(sabnzbd.cfg.history_retention())
if to_keep > 0:
logging.info('Removing all but last %s completed jobs from history', to_keep)
return self.execute("""DELETE FROM history WHERE id NOT IN ( SELECT id FROM history WHERE status = 'Completed' ORDER BY completed DESC LIMIT ? )""", (to_keep,), save=True)
def add_history_db(self, nzo, storage, path, postproc_time, script_output, script_line):
""" Add a new job entry to the database """
t = build_history_info(nzo, storage, path, postproc_time, script_output, script_line)
@@ -540,6 +564,13 @@ def unpack_history_info(item):
return item
def midnight_history_purge():
logging.info('Scheduled history purge')
history_db = HistoryDB()
history_db.auto_history_purge()
history_db.close()
def decode_factory(text):
""" Recursively looks through the supplied argument
and converts and text to Unicode

View File

@@ -177,7 +177,7 @@ class DirectUnpacker(threading.Thread):
# Add to success
self.success_sets.append(self.cur_setname)
logging.info('DirectUnpack completed for %s', self.cur_setname)
self.nzo.set_action_line(T('Unpacking'), T('Completed'))
self.nzo.set_action_line(T('Direct Unpack'), T('Completed'))
# Write current log
unrar_log.append(linebuf.strip())
@@ -222,7 +222,7 @@ class DirectUnpacker(threading.Thread):
if not last_volume_linebuf or last_volume_linebuf != linebuf:
# Next volume
self.cur_volume += 1
self.nzo.set_action_line(T('Unpacking'), self.get_formatted_stats())
self.nzo.set_action_line(T('Direct Unpack'), self.get_formatted_stats())
logging.info('DirectUnpacked volume %s for %s', self.cur_volume, self.cur_setname)
last_volume_linebuf = linebuf
@@ -245,17 +245,23 @@ class DirectUnpacker(threading.Thread):
if self in ACTIVE_UNPACKERS:
ACTIVE_UNPACKERS.remove(self)
# Set the thread to killed so it never gets restarted by accident
self.killed = True
def have_next_volume(self):
""" Check if next volume of set is available, start
from the end of the list where latest completed files are """
from the end of the list where latest completed files are
Make sure that files are 100% written to disk by checking md5sum
"""
for nzf_search in reversed(self.nzo.finished_files):
if nzf_search.setname == self.cur_setname and nzf_search.vol == (self.cur_volume+1):
if nzf_search.setname == self.cur_setname and nzf_search.vol == (self.cur_volume+1) and nzf_search.md5sum:
return nzf_search
return False
def wait_for_next_volume(self):
""" Wait for the correct volume to appear
But stop if it was killed or the NZB is done """
But stop if it was killed or the NZB is done
"""
while not self.have_next_volume() and not self.killed and self.nzo.files:
with self.next_file_lock:
self.next_file_lock.wait()
@@ -353,7 +359,8 @@ class DirectUnpacker(threading.Thread):
def analyze_rar_filename(filename):
""" Extract volume number and setname from rar-filenames
Both ".part01.rar" and ".r01" """
Both ".part01.rar" and ".r01"
"""
m = RAR_NR.search(filename)
if m:
if m.group(4):

View File

@@ -1305,7 +1305,7 @@ SWITCH_LIST = \
'safe_postproc', 'no_dupes', 'replace_spaces', 'replace_dots',
'ignore_samples', 'pause_on_post_processing', 'nice', 'ionice',
'pre_script', 'pause_on_pwrar', 'sfv_check', 'folder_rename', 'load_balancing',
'quota_size', 'quota_day', 'quota_resume', 'quota_period',
'quota_size', 'quota_day', 'quota_resume', 'quota_period', 'history_retention',
'pre_check', 'max_art_tries', 'fail_hopeless_jobs', 'enable_all_par',
'enable_recursive', 'no_series_dupes', 'script_can_fail', 'new_nzb_on_failure',
'unwanted_extensions', 'action_on_unwanted_extensions', 'sanitize_safe',
@@ -1384,7 +1384,7 @@ SPECIAL_BOOL_LIST = \
)
SPECIAL_VALUE_LIST = \
('size_limit', 'folder_max_length', 'fsys_type', 'movie_rename_limit', 'nomedia_marker',
'req_completion_rate', 'wait_ext_drive', 'history_limit', 'show_sysload',
'req_completion_rate', 'wait_ext_drive', 'show_sysload',
'direct_unpack_threads', 'ipv6_servers', 'selftest_host', 'rating_host'
)
SPECIAL_LIST_LIST = ('rss_odd_titles', 'quick_check_ext_ignore')
@@ -1612,9 +1612,9 @@ class ConfigServer(object):
server_names = sorted(servers.keys(), key=lambda svr: '%d%02d%s' % (int(not servers[svr].enable()), servers[svr].priority(), servers[svr].displayname().lower()))
for svr in server_names:
new.append(servers[svr].get_dict(safe=True))
t, m, w, d = BPSMeter.do.amounts(svr)
t, m, w, d, timeline = BPSMeter.do.amounts(svr)
if t:
new[-1]['amounts'] = to_units(t), to_units(m), to_units(w), to_units(d)
new[-1]['amounts'] = to_units(t), to_units(m), to_units(w), to_units(d), timeline
conf['servers'] = new
conf['cats'] = list_cats(default=True)
conf['have_ssl_context'] = sabnzbd.HAVE_SSL_CONTEXT

View File

@@ -1461,7 +1461,7 @@ def starts_with_path(path, prefix):
def set_chmod(path, permissions, report):
""" Set 'permissions' on 'path', report any errors when 'report' is True """
try:
logging.debug('Applying %s to %s', permissions, path)
logging.debug('Applying permissions %s (octal) to %s', oct(permissions), path)
os.chmod(path, permissions)
except:
lpath = path.lower()

View File

@@ -851,17 +851,23 @@ class NzbObject(TryList):
if duplicate and ((not series and cfg.no_dupes() == 3) or (series and cfg.no_series_dupes() == 3)):
if cfg.warn_dupl_jobs():
logging.warning(T('Failing duplicate NZB "%s"'), filename)
# Move to history, utlizing the same code as accept&fail from pre-queue script
# Move to history, utilizing the same code as accept&fail from pre-queue script
self.fail_msg = T('Duplicate NZB')
accept = 2
duplicate = False
if duplicate or self.priority == DUP_PRIORITY:
if cfg.warn_dupl_jobs():
logging.warning(T('Pausing duplicate NZB "%s"'), filename)
self.duplicate = True
self.pause()
self.priority = NORMAL_PRIORITY
if cfg.no_dupes() == 4 or cfg.no_series_dupes() == 4:
if cfg.warn_dupl_jobs():
logging.warning('%s: "%s"', T('Duplicate NZB'), filename)
self.duplicate = True
self.priority = NORMAL_PRIORITY
else:
if cfg.warn_dupl_jobs():
logging.warning(T('Pausing duplicate NZB "%s"'), filename)
self.duplicate = True
self.pause()
self.priority = NORMAL_PRIORITY
if self.priority == PAUSED_PRIORITY:
self.pause()
@@ -1057,9 +1063,12 @@ class NzbObject(TryList):
if not found:
# Add extra parfiles when there was a damaged article and not pre-checking
if self.extrapars and not self.precheck:
self.abort_direct_unpacker()
self.prospective_add(nzf)
# Sometimes a few CRC errors are still fine, so we continue
if self.bad_articles > 5:
self.abort_direct_unpacker()
post_done = False
if not self.files:
post_done = True

View File

@@ -547,6 +547,8 @@ def process_job(nzo):
# Add the nzo to the database. Only the path, script and time taken is passed
# Other information is obtained from the nzo
history_db.add_history_db(nzo, clip_path(workdir_complete), nzo.downpath, postproc_time, script_log, script_line)
# Purge items
history_db.auto_history_purge()
# The connection is only used once, so close it here
history_db.close()
sabnzbd.history_updated()

View File

@@ -193,6 +193,11 @@ def init():
__SCHED.add_daytime_task(action, 'quota_reset', range(1, 8), None, (hour, minute),
kronos.method.sequential, [], None)
if sabnzbd.misc.int_conv(cfg.history_retention()) > 0:
logging.info('Setting schedule for midnight auto history-purge')
__SCHED.add_daytime_task(sabnzbd.database.midnight_history_purge, 'midnight_history_purge', range(1, 8), None, (0, 0),
kronos.method.sequential, [], None)
logging.info('Setting schedule for midnight BPS reset')
__SCHED.add_daytime_task(sabnzbd.bpsmeter.midnight_action, 'midnight_bps', range(1, 8), None, (0, 0),
kronos.method.sequential, [], None)

View File

@@ -102,6 +102,18 @@ SKIN_TEXT = {
'week' : TT('week'),
'month' : TT('Month'),
'year' : TT('Year'),
'January': TT('January'),
'February': TT('February'),
'March': TT('March'),
'April': TT('April'),
'May': TT('May'),
'June': TT('June'),
'July': TT('July'),
'August': TT('August'),
'September': TT('September'),
'October': TT('October'),
'November': TT('November'),
'December': TT('December'),
'monday' : TT('Monday'),
'tuesday' : TT('Tuesday'),
'wednesday' : TT('Wednesday'),
@@ -351,6 +363,13 @@ SKIN_TEXT = {
'explain-cache_limitstr' : TT('Cache articles in memory to reduce disk access.<br /><i>In bytes, optionally follow with K,M,G. For example: "64M" or "128M"</i>'),
'opt-cleanup_list' : TT('Cleanup List'),
'explain-cleanup_list' : TT('List of file extensions that should be deleted after download.<br />For example: <b>nfo</b> or <b>nfo, sfv</b>'),
'opt-history_retention' : TT('History Retention'),
'explain-history_retention' : TT('Automatically delete completed jobs from History. Beware that Duplicate Detection and some external tools rely on History information.'),
'history_retention-all' : TT('Keep all jobs'),
'history_retention-number' : TT('Keep maximum number of completed jobs'),
'history_retention-days' : TT('Keep completed jobs maximum number of days'),
'history_retention-none' : TT('Do not keep any completed jobs'),
'history_retention-limit': TT('History item limit'),
'button-saveChanges' : TT('Save Changes'),
'button-restoreDefaults' : TT('Restore Defaults'),
'explain-restoreDefaults' : TT('Reset'),
@@ -425,9 +444,10 @@ SKIN_TEXT = {
'opt-no_series_dupes' : TT('Detect duplicate episodes in series'),
'explain-no_series_dupes' : TT('Detect identical episodes in series (based on "name/season/episode" of items in your History)'),
'nodupes-off' : TT('Off'), #: Three way switch for duplicates
'nodupes-ignore' : TT('Discard'), #: Three way switch for duplicates
'nodupes-pause' : TT('Pause'), #: Three way switch for duplicates
'nodupes-fail' : TT('Fail job (move to History)'), #: Three way switch for duplicates
'nodupes-ignore' : TT('Discard'), #: Four way switch for duplicates
'nodupes-pause' : TT('Pause'), #: Four way switch for duplicates
'nodupes-fail' : TT('Fail job (move to History)'), #: Four way switch for duplicates
'nodupes-tag' : TT('Tag job'), #: Four way switch for duplicates
'abort' : TT('Abort'), #: Three way switch for encrypted posts
'opt-action_on_unwanted_extensions' : TT('Action when unwanted extension detected'),
'explain-action_on_unwanted_extensions' : TT('Action when an unwanted extension is detected in RAR files'),
@@ -788,6 +808,7 @@ SKIN_TEXT = {
'Glitter-page' : TT('page'),
'Glitter-loading' : TT('Loading'),
'Glitter-articles' : TT('articles'),
'Glitter-rename' : TT('Rename'),
'Glitter-repairQueue' : TT('Queue repair'),
'Glitter-showActiveConnections' : TT('Show active connections'),
'Glitter-unblockServer' : TT('Unblock'),

View File

@@ -47,6 +47,11 @@ class Wizard(object):
@cherrypy.expose
def index(self, **kwargs):
""" Show the language selection page """
if cfg.configlock() or not sabnzbd.interface.check_access():
return sabnzbd.interface.Protected()
if not sabnzbd.interface.check_login():
raise sabnzbd.interface.NeedLogin()
info = self.info.copy()
lng = None
if sabnzbd.WIN32:
@@ -72,15 +77,25 @@ class Wizard(object):
@cherrypy.expose
def exit(self, **kwargs):
""" Stop SABnzbd """
yield "Initiating shutdown..."
if cfg.configlock() or not sabnzbd.interface.check_access():
return sabnzbd.interface.Protected()
if not sabnzbd.interface.check_login():
raise sabnzbd.interface.NeedLogin()
logging.info('Shutdown requested by wizard')
sabnzbd.halt()
yield "<br>SABnzbd-%s shutdown finished" % sabnzbd.__version__
cherrypy.engine.exit()
sabnzbd.SABSTOP = True
return T('SABnzbd shutdown finished')
@cherrypy.expose
def one(self, **kwargs):
""" Accept language and show server page """
if cfg.configlock() or not sabnzbd.interface.check_access():
return sabnzbd.interface.Protected()
if not sabnzbd.interface.check_login():
raise sabnzbd.interface.NeedLogin()
language = kwargs.get('lang') if kwargs.get('lang') else cfg.language()
cfg.language.set(language)
set_language(language)
@@ -125,6 +140,11 @@ class Wizard(object):
@cherrypy.expose
def two(self, **kwargs):
""" Accept server and show the final page for restart """
if cfg.configlock() or not sabnzbd.interface.check_access():
return sabnzbd.interface.Protected()
if not sabnzbd.interface.check_login():
raise sabnzbd.interface.NeedLogin()
# Save server details
if kwargs:
kwargs['enable'] = 1