mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-24 08:08:37 -05:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33a7874416 | ||
|
|
3ead1b56cd | ||
|
|
2a59d5f54d | ||
|
|
cbea4e3fd7 | ||
|
|
0c7e77ca65 | ||
|
|
f9b93fad93 | ||
|
|
fde8014acd | ||
|
|
692a3eaf45 | ||
|
|
809b11c2f3 | ||
|
|
f2ac2db86f | ||
|
|
9dc6fa57ac | ||
|
|
9fa017989c | ||
|
|
80c6626e58 | ||
|
|
b5d6078650 | ||
|
|
a31673ac3e | ||
|
|
f18ec189b8 | ||
|
|
b3623bdd1f | ||
|
|
8247451b28 | ||
|
|
009ee14b14 | ||
|
|
2d16c19f18 | ||
|
|
8f84cda308 | ||
|
|
9397a02bfe | ||
|
|
6c9a26b05e | ||
|
|
01d668ac08 | ||
|
|
38a943dc12 | ||
|
|
9b8dbdb179 | ||
|
|
637c00dcff | ||
|
|
1a6db92920 | ||
|
|
74c9444f5f | ||
|
|
abb0226d5a | ||
|
|
739921e423 | ||
|
|
57780c280c | ||
|
|
0fbf79f5af |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -10,17 +10,14 @@ srcdist/
|
||||
# Generated email templates
|
||||
email/*.tmpl
|
||||
|
||||
# Romanian ro.po is generated from ro.px, due to mapping to latin-1
|
||||
ro.po
|
||||
|
||||
# Build results
|
||||
SABnzbd*.zip
|
||||
SABnzbd*.exe
|
||||
SABnzbd*.gz
|
||||
SABnzbd*.dmg
|
||||
|
||||
# WingIDE project file
|
||||
*.wpr
|
||||
# WingIDE project files
|
||||
*.wp[ru]
|
||||
|
||||
# General junk
|
||||
*.keep
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
*******************************************
|
||||
*** This is SABnzbd 1.1.x ***
|
||||
*** This is SABnzbd 1.1.1 ***
|
||||
*******************************************
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
SABnzbd 1.1.0
|
||||
|
||||
SABnzbd 1.1.1
|
||||
-------------------------------------------------------------------------------
|
||||
0) LICENSE
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
Windows-only:
|
||||
If you keep having trouble with par2 multicore you can disable it
|
||||
in Config->Switches.
|
||||
This will force the use of the old and tried, but slower par2-classic program.
|
||||
This will force the use of the old and tried, but slower par2cmdline program.
|
||||
|
||||
- A bug in Windows 7 may cause severe memory leaks when you use SABnzbd in
|
||||
combination with some virus scanners and firewalls.
|
||||
|
||||
4
PKG-INFO
4
PKG-INFO
@@ -1,7 +1,7 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 1.2.0
|
||||
Summary: SABnzbd-1.2.0
|
||||
Version: 1.1.1
|
||||
Summary: SABnzbd-1.1.1
|
||||
Home-page: http://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
|
||||
29
README.mkd
29
README.mkd
@@ -1,14 +1,27 @@
|
||||
Release Notes - SABnzbd 1.2.0
|
||||
Release Notes - SABnzbd 1.1.1
|
||||
===============================
|
||||
|
||||
## What's new in 1.2.0
|
||||
|
||||
|
||||
## Changes:
|
||||
|
||||
|
||||
## Bug fixes
|
||||
## What's new in 1.1.1
|
||||
|
||||
## Buf fixes
|
||||
- 8th parameter for user-script wasn't passed correctly.
|
||||
- Fix broken HTTPS port binding
|
||||
- Only allow binding to IPv6 when ipv6_hosting enabled
|
||||
- Allow also "vol01-03.par" on top of "vol01+03.par"
|
||||
- Glitter didn't allow removal of a set job-password
|
||||
- Unicode failed downloads were seen as orphaned jobs
|
||||
- QuickCheck would fail unicode files
|
||||
- Clean-up all par2 of a set
|
||||
- Fix retry_all API-call
|
||||
- Make sure we show results when less than 1 page
|
||||
- Fixed email notifications to smtp2go.com (and possibly others)
|
||||
- Replaced par2-classic with par2cmdline (will fix some verification hangups)
|
||||
- Fix problem with Config pages on mobile browsers
|
||||
- Updated INSTALL.txt
|
||||
- Button to regenerate a self-signed HTTPS certificate (to update to modern standards)
|
||||
- Restored download speed for Unix (and some other) systems
|
||||
- Fixed yEnc crash that occurred on some Windows systems
|
||||
- Small UI fixes
|
||||
|
||||
|
||||
## About
|
||||
|
||||
12
SABnzbd.py
12
SABnzbd.py
@@ -480,7 +480,7 @@ def print_modules():
|
||||
logging.error(T('par2 binary... NOT found!'))
|
||||
|
||||
if sabnzbd.newsunpack.PAR2C_COMMAND:
|
||||
logging.info("par2-classic binary... found (%s)", sabnzbd.newsunpack.PAR2C_COMMAND)
|
||||
logging.info("par2cmdline binary... found (%s)", sabnzbd.newsunpack.PAR2C_COMMAND)
|
||||
|
||||
if sabnzbd.newsunpack.RAR_COMMAND:
|
||||
logging.info("UNRAR binary... found (%s)", sabnzbd.newsunpack.RAR_COMMAND)
|
||||
@@ -533,7 +533,8 @@ def all_localhosts():
|
||||
ips = []
|
||||
for item in info:
|
||||
item = item[4][0]
|
||||
if item not in ips:
|
||||
# Only return IPv6 when enabled
|
||||
if item not in ips and ('::1' not in item or sabnzbd.cfg.ipv6_hosting()):
|
||||
ips.append(item)
|
||||
return ips
|
||||
|
||||
@@ -685,14 +686,13 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
def attach_server(host, port, cert=None, key=None, chain=None):
|
||||
""" Define and attach server, optionally HTTPS """
|
||||
if sabnzbd.cfg.ipv6_hosting() or '::1' not in host:
|
||||
http_server = _cpwsgi_server.CPWSGIServer()
|
||||
http_server = cherrypy._cpserver.Server()
|
||||
http_server.bind_addr = (host, port)
|
||||
if cert and key:
|
||||
http_server.ssl_certificate = cert
|
||||
http_server.ssl_private_key = key
|
||||
http_server.ssl_certificate_chain = chain
|
||||
adapter = _cpserver.ServerAdapter(cherrypy.engine, http_server, http_server.bind_addr)
|
||||
adapter.subscribe()
|
||||
http_server.subscribe()
|
||||
|
||||
|
||||
def is_sabnzbd_running(url, timeout=None):
|
||||
@@ -1400,7 +1400,7 @@ def main():
|
||||
hosts[1] = '::1'
|
||||
|
||||
# The Windows binary requires numeric localhost as primary address
|
||||
if multilocal and cherryhost == 'localhost':
|
||||
if cherryhost == 'localhost':
|
||||
cherryhost = hosts[0]
|
||||
|
||||
if enable_https:
|
||||
|
||||
@@ -80,14 +80,18 @@
|
||||
<div class="field-pair">
|
||||
<label class="config" for="inet_exposure">$T('opt-inet_exposure')</label>
|
||||
<select name="inet_exposure" id="inet_exposure" class="select">
|
||||
<option value="0" <!--#if $inet_exposure == 0 then 'selected="selected"' else ""#-->>$T('inet-local')</option>
|
||||
<option value="1" <!--#if $inet_exposure == 1 then 'selected="selected"' else ""#-->>$T('inet-nzb')</option>
|
||||
<option value="2" <!--#if $inet_exposure == 2 then 'selected="selected"' else ""#-->>$T('inet-api')</option>
|
||||
<option value="3" <!--#if $inet_exposure == 3 then 'selected="selected"' else ""#-->>$T('inet-fullapi')</option>
|
||||
<option value="4" <!--#if $inet_exposure == 4 then 'selected="selected"' else ""#-->>$T('inet-ui')</option>
|
||||
<option value="5" <!--#if $inet_exposure == 5 then 'selected="selected"' else ""#-->>$T('inet-ui') - $T('inet-external_login')</option>
|
||||
<optgroup label="API">
|
||||
<option value="0" <!--#if $inet_exposure == 0 then 'selected="selected"' else ""#-->>$T('inet-local')</option>
|
||||
<option value="1" <!--#if $inet_exposure == 1 then 'selected="selected"' else ""#-->>$T('inet-nzb')</option>
|
||||
<option value="2" <!--#if $inet_exposure == 2 then 'selected="selected"' else ""#-->>$T('inet-api')</option>
|
||||
<option value="3" <!--#if $inet_exposure == 3 then 'selected="selected"' else ""#-->>$T('inet-fullapi')</option>
|
||||
</optgroup>
|
||||
<optgroup label="$T('inet-fullapi') & $T('opt-web_dir')">
|
||||
<option value="4" <!--#if $inet_exposure == 4 then 'selected="selected"' else ""#-->>$T('inet-ui')</option>
|
||||
<option value="5" <!--#if $inet_exposure == 5 then 'selected="selected"' else ""#-->>$T('inet-ui') - $T('inet-external_login')</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<span class="desc">$T('explain-inet_exposure')</span>
|
||||
<span class="desc">$T('explain-inet_exposure').replace('.','.<br>')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="local_ranges">$T('opt-local_ranges')</label>
|
||||
@@ -170,10 +174,15 @@
|
||||
</div><!-- /col2 -->
|
||||
<div class="col1">
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="bandwidth_max">$T('opt-bandwidth_max')</label>
|
||||
<input type="text" name="bandwidth_max" id="bandwidth_max" value="$bandwidth_max" class="smaller_input" />
|
||||
<span class="desc">$T('explain-bandwidth_max')<br />$T('wizard-bandwidth-explain')</span>
|
||||
<div class="field-pair value-and-select">
|
||||
<label class="config" for="bandwidth_max_value">$T('opt-bandwidth_max')</label>
|
||||
<input type="number" name="bandwidth_max_value" id="bandwidth_max_value" class="smaller_input" />
|
||||
<select name="bandwidth_max_dropdown" id="bandwidth_max_dropdown">
|
||||
<option value="">B/s</option>
|
||||
<option value="K">KB/s</option>
|
||||
<option value="M" selected>MB/s</option>
|
||||
</select>
|
||||
<input type="hidden" name="bandwidth_max" id="bandwidth_max" value="$bandwidth_max" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="bandwidth_perc">$T('opt-bandwidth_perc')</label>
|
||||
@@ -282,6 +291,22 @@
|
||||
if(\$('#https_cert').val() != 'server.cert') {
|
||||
\$('.generate_cert').attr('disabled', 'disabled')
|
||||
}
|
||||
|
||||
// Parse the text
|
||||
var bandwidthLimit = \$('#bandwidth_max').val()
|
||||
if(bandwidthLimit) {
|
||||
var bandwithLimitNumber = parseFloat(bandwidthLimit)
|
||||
var bandwithLimitText = bandwidthLimit.replace(/[^a-zA-Z]+/g, '');
|
||||
\$('#bandwidth_max_value').val(bandwithLimitNumber)
|
||||
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
|
||||
}
|
||||
|
||||
|
||||
// Update the value
|
||||
\$('#bandwidth_max_value, #bandwidth_max_dropdown').on('change', function() {
|
||||
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -279,8 +279,8 @@
|
||||
this.submit()
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
Color the priority labels
|
||||
**/
|
||||
// They are already sorted
|
||||
@@ -298,7 +298,7 @@
|
||||
\$(this).css('background-color', theColor)
|
||||
})
|
||||
|
||||
/**
|
||||
/**
|
||||
Message on no Default category selected
|
||||
**/
|
||||
function checkServerCats() {
|
||||
@@ -309,10 +309,10 @@
|
||||
// See if this server is enabled
|
||||
if(!\$(this).parents('.section').find('.col2').hasClass('server-disabled') ) {
|
||||
// Is there Default?
|
||||
if(\$(this).val().indexOf('Default') > -1) {
|
||||
if(\$(this).val() && \$(this).val().indexOf('Default') > -1) {
|
||||
// Hide
|
||||
\$('.alert-no-category').hide()
|
||||
hasDefault = true
|
||||
hasDefault = true
|
||||
// All good!
|
||||
return true
|
||||
}
|
||||
@@ -323,8 +323,8 @@
|
||||
}
|
||||
|
||||
\$('select[name="categories"]').on('change', checkServerCats)
|
||||
checkServerCats()
|
||||
|
||||
checkServerCats()
|
||||
|
||||
/**
|
||||
Click events
|
||||
**/
|
||||
|
||||
@@ -969,6 +969,12 @@ input[type="checkbox"] {
|
||||
.main-restarting small {
|
||||
font-size: 3rem !important;
|
||||
}
|
||||
.value-and-select input {
|
||||
margin-right: -5px;
|
||||
}
|
||||
.value-and-select select {
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.dotOne, .dotTwo, .dotThree {
|
||||
opacity: 0;
|
||||
@@ -1072,6 +1078,10 @@ input[type="checkbox"] {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.navbar .nav>li>a {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.section .col2 h3 {
|
||||
padding-right: 25px;
|
||||
|
||||
@@ -221,7 +221,7 @@ function do_restart() {
|
||||
if($('#enable_https').is(':checked') && window.location.protocol == 'https:') {
|
||||
// Https on and we visited this page from HTTPS
|
||||
var urlProtocol = 'https:';
|
||||
var urlPort = $('#https_port').val();
|
||||
var urlPort = $('#https_port').val() ? $('#https_port').val() : $('#port').val();
|
||||
} else {
|
||||
// Regular
|
||||
var urlProtocol = 'http:';
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<li class="active"><a href="#options-status" data-toggle="tab">$T('menu-cons')</a></li>
|
||||
<li><a href="#options_connections" data-toggle="tab">$T('connections')</a></li>
|
||||
<li><a href="#options-orphans" data-toggle="tab">$T('Glitter-orphanedJobs') <!-- ko if: statusInfo.folders().length > 0 --><span class="label label-warning" data-bind="text: statusInfo.folders().length"></span><!-- /ko --></a></li>
|
||||
<li><a href="#options-interface" data-toggle="tab">$T('Glitter-interfaceOptions')</a></li>
|
||||
<li><a href="#options-interface" data-toggle="tab">$T('Glitter-interfaceOptions')</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade in active" id="options-status">
|
||||
@@ -144,9 +144,9 @@
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasDiskStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<hr />
|
||||
<hr />
|
||||
<div class="row options-function-box">
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-6">
|
||||
<a href="#" data-bind="click: forceDisconnect" class="btn btn-default "><span class="glyphicon glyphicon-minus-sign"></span> $T('link-forceDisc')</a>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
@@ -156,7 +156,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row options-function-box">
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-6">
|
||||
<a href="./status/showlog?session=$session" target="_blank" class="btn btn-default"><span class="glyphicon glyphicon-file"></span> $T('link-showLog')</a>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
@@ -174,13 +174,13 @@
|
||||
<div class="tab-pane fade" id="options_connections">
|
||||
<div class="options-switch">
|
||||
<label>
|
||||
<input type="checkbox" value="1" name="showConnections" data-bind="checked: showActiveConnections" />
|
||||
<input type="checkbox" value="1" name="showConnections" data-bind="checked: showActiveConnections" />
|
||||
<span>$T('Glitter-showActiveConnections')</span>
|
||||
</label>
|
||||
</div>
|
||||
<div data-bind="foreach: statusInfo.servers">
|
||||
<div class="options-server-box">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('swtag-server')</div>
|
||||
<div class="col-sm-6">
|
||||
@@ -195,7 +195,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6"># $T('connections')</div>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: serverconnections().length"></span> /
|
||||
<span data-bind="text: serverconnections().length"></span> /
|
||||
<span data-bind="text: servertotalconn"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -216,7 +216,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ko if: serverconnections().length > 0 -->
|
||||
<table class="table table-hover table-server-connections" data-bind="visible: \$root.showActiveConnections()">
|
||||
<thead>
|
||||
@@ -245,8 +245,8 @@
|
||||
<h4>$T('none')</h4>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: statusInfo.folders().length > 0 -->
|
||||
<a href="#" class="hover-button process-all-orphaned" data-bind="click: removeAllOrphaned">$T('Glitter-purgeOrphaned') <span class="glyphicon glyphicon-trash"></span></a>
|
||||
<a href="#" class="hover-button process-all-orphaned" data-bind="click: addAllOrphaned">$T('Glitter-retryAllOrphaned') <span class="glyphicon glyphicon-plus-sign"></span></a>
|
||||
<a href="#" class="hover-button process-all-orphaned" data-bind="click: removeAllOrphaned">$T('Glitter-purgeOrphaned') <span class="glyphicon glyphicon-trash"></span></a>
|
||||
<a href="#" class="hover-button process-all-orphaned" data-bind="click: addAllOrphaned">$T('Glitter-retryAllOrphaned') <span class="glyphicon glyphicon-plus-sign"></span></a>
|
||||
<div class="clearfix"></div>
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
@@ -272,7 +272,7 @@
|
||||
<form class="form-horizontal" onsubmit="return false;">
|
||||
<div class="options-switch">
|
||||
<label>
|
||||
<input type="checkbox" name="useGlobalOptions" value="true" data-bind="checked: useGlobalOptions" />
|
||||
<input type="checkbox" name="useGlobalOptions" value="true" data-bind="checked: useGlobalOptions" />
|
||||
<span>$T('Glitter-useGlobalOptions')</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -646,8 +646,8 @@
|
||||
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-failed"><span class="glyphicon glyphicon-floppy-remove"></span> $T('purgeFailed')</button>
|
||||
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purgeremove-failed"><span class="glyphicon glyphicon-floppy-remove"></span> $T('purgeFailed-Files')</button><hr />
|
||||
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-completed"><span class="glyphicon glyphicon-floppy-saved"></span> $T('purgeCompl')</button><hr />
|
||||
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-page"><span class="glyphicon glyphicon-check"></span> $T('purgeHist') ($T('Glitter-page')) <span class="label label-default" data-bind="text: history.historyItems().length"></span></button>
|
||||
|
||||
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-page"><span class="glyphicon glyphicon-check"></span> $T('purgePage') <span class="label label-default" data-bind="text: history.historyItems().length"></span></button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ function Fileslisting(parent) {
|
||||
// Update
|
||||
self.currentItem = queue_item;
|
||||
self.fileItems.removeAll()
|
||||
self.triggerUpdate()
|
||||
self.triggerUpdate()
|
||||
|
||||
// Update name/password
|
||||
self.filelist_name(self.currentItem.name())
|
||||
@@ -22,7 +22,7 @@ function Fileslisting(parent) {
|
||||
// Hide ok button and reset
|
||||
$('#modal-item-filelist .glyphicon-floppy-saved').hide()
|
||||
$('#modal-item-filelist .glyphicon-lock').show()
|
||||
|
||||
|
||||
// Set state of the check-all
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
|
||||
@@ -63,7 +63,7 @@ function Fileslisting(parent) {
|
||||
// They cause problems because they can have the same filename
|
||||
// as files that we do want to be updated.. The slot.id is not unique!
|
||||
if(slot.status == "queued") return false;
|
||||
|
||||
|
||||
// Update the rest
|
||||
existingItem.updateFromData(slot);
|
||||
} else {
|
||||
@@ -89,7 +89,7 @@ function Fileslisting(parent) {
|
||||
})
|
||||
}
|
||||
|
||||
// Set update
|
||||
// Set update
|
||||
self.setUpdate = function() {
|
||||
self.updateTimeout = setTimeout(function() {
|
||||
self.triggerUpdate()
|
||||
@@ -143,10 +143,13 @@ function Fileslisting(parent) {
|
||||
|
||||
// For changing the passwords
|
||||
self.setNzbPassword = function() {
|
||||
// Activate with this weird URL "API"
|
||||
callSpecialAPI("./nzb/" + self.currentItem.id + "/save/", {
|
||||
name: self.currentItem.name(),
|
||||
password: $('#nzb_password').val()
|
||||
// Have to also send the current name for it to work
|
||||
callAPI({
|
||||
mode: 'queue',
|
||||
name: 'rename',
|
||||
value: self.currentItem.id,
|
||||
value2: self.currentItem.name(),
|
||||
value3: $('#nzb_password').val()
|
||||
}).then(function() {
|
||||
// Refresh, reset and close
|
||||
parent.refresh()
|
||||
@@ -156,17 +159,17 @@ function Fileslisting(parent) {
|
||||
})
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check all
|
||||
self.checkAllFiles = function(item, event) {
|
||||
// Get which ones we care about
|
||||
var allChecks = $('#modal-item-files .files-sortable input').filter(':not(:disabled):visible');
|
||||
|
||||
|
||||
// We need to re-evaltuate the state of this check-all
|
||||
// Otherwise the 'inderterminate' will be overwritten by the click event!
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
|
||||
// Now we can check what happend
|
||||
|
||||
// Now we can check what happend
|
||||
if(event.target.indeterminate) {
|
||||
allChecks.filter(":checked").prop('checked', false)
|
||||
} else {
|
||||
@@ -179,7 +182,7 @@ function Fileslisting(parent) {
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// For selecting range and the check-all button
|
||||
self.checkSelectRange = function(data, event) {
|
||||
if(event.shiftKey) {
|
||||
@@ -273,14 +276,23 @@ function paginationModel(parent) {
|
||||
if(parent.totalItems() <= parent.paginationLimit()) {
|
||||
// Empty it
|
||||
self.nrPages(1)
|
||||
|
||||
// Reset all to make sure we see something
|
||||
self.currentPage(1);
|
||||
self.currentStart(0);
|
||||
|
||||
// Are we on next page?
|
||||
if(self.currentPage() > 1) {
|
||||
// Force full update
|
||||
parent.parent.refresh(true);
|
||||
}
|
||||
|
||||
// Move to current page
|
||||
self.currentPage(1);
|
||||
|
||||
// Force full update
|
||||
parent.parent.refresh(true);
|
||||
} else {
|
||||
// Calculate number of pages needed
|
||||
var newNrPages = Math.ceil(parent.totalItems() / parent.paginationLimit())
|
||||
|
||||
|
||||
// Make sure the current page still exists
|
||||
if(self.currentPage() > newNrPages) {
|
||||
self.moveToPage(newNrPages);
|
||||
@@ -289,7 +301,7 @@ function paginationModel(parent) {
|
||||
|
||||
// All the cases
|
||||
if(newNrPages > 7) {
|
||||
// Do we show the first ones
|
||||
// Do we show the first ones
|
||||
if(self.currentPage() < 5) {
|
||||
// Just add the first 4
|
||||
$.each(new Array(5), function(index) {
|
||||
@@ -300,7 +312,7 @@ function paginationModel(parent) {
|
||||
// Last one
|
||||
self.allpages.push(self.addPaginationPageLink(newNrPages))
|
||||
} else {
|
||||
// Always add the first
|
||||
// Always add the first
|
||||
self.allpages.push(self.addPaginationPageLink(1))
|
||||
// Dots
|
||||
self.allpages.push(self.addDots())
|
||||
|
||||
@@ -35,16 +35,16 @@ function HistoryListModel(parent) {
|
||||
var itemIds = $.map(self.historyItems(), function(i) {
|
||||
return i.historyStatus.nzo_id();
|
||||
});
|
||||
|
||||
|
||||
// For new items
|
||||
var newItems = [];
|
||||
var newItems = [];
|
||||
$.each(data.slots, function(index, slot) {
|
||||
var existingItem = ko.utils.arrayFirst(self.historyItems(), function(i) {
|
||||
return i.historyStatus.nzo_id() == slot.nzo_id;
|
||||
});
|
||||
// Set index in the results
|
||||
slot.index = index
|
||||
|
||||
|
||||
// Update or add?
|
||||
if(existingItem) {
|
||||
existingItem.updateFromData(slot);
|
||||
@@ -54,7 +54,7 @@ function HistoryListModel(parent) {
|
||||
newItems.push(new HistoryModel(self, slot));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Remove all items
|
||||
if(itemIds.length == self.paginationLimit()) {
|
||||
// Replace it, so only 1 Knockout DOM-update!
|
||||
@@ -69,7 +69,7 @@ function HistoryListModel(parent) {
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Add new ones
|
||||
if(newItems.length > 0) {
|
||||
ko.utils.arrayPushAll(self.historyItems, newItems);
|
||||
@@ -100,7 +100,7 @@ function HistoryListModel(parent) {
|
||||
};
|
||||
|
||||
// Save pagination state
|
||||
self.paginationLimit.subscribe(function(newValue) {
|
||||
self.paginationLimit.subscribe(function(newValue) {
|
||||
// Save in config if global config
|
||||
if(self.parent.useGlobalOptions()) {
|
||||
callAPI({
|
||||
@@ -121,7 +121,7 @@ function HistoryListModel(parent) {
|
||||
data.append("password", $('#retry_job_password').val());
|
||||
data.append("session", apiKey);
|
||||
|
||||
// Add
|
||||
// Add
|
||||
$.ajax({
|
||||
url: "./retry_pp",
|
||||
type: "POST",
|
||||
@@ -137,7 +137,7 @@ function HistoryListModel(parent) {
|
||||
$('.btn-file em').html(glitterTranslate.chooseFile + '…')
|
||||
form.reset()
|
||||
}
|
||||
|
||||
|
||||
// Searching in history (rate-limited in decleration)
|
||||
self.searchTerm.subscribe(function() {
|
||||
// Make sure we refresh
|
||||
@@ -148,7 +148,7 @@ function HistoryListModel(parent) {
|
||||
self.pagination.moveToPage(1);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Clear searchterm
|
||||
self.clearSearchTerm = function(data, event) {
|
||||
// Was it escape key or click?
|
||||
@@ -166,9 +166,10 @@ function HistoryListModel(parent) {
|
||||
// Need to return true to allow typing
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 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())
|
||||
@@ -176,17 +177,18 @@ function HistoryListModel(parent) {
|
||||
$('#history-options a').tooltip('hide')
|
||||
// Force refresh
|
||||
self.parent.refresh(true)
|
||||
|
||||
}
|
||||
|
||||
// Empty history options
|
||||
self.emptyHistory = function(data, event) {
|
||||
// Make sure no flickering
|
||||
self.isLoading(true)
|
||||
|
||||
|
||||
// What event?
|
||||
var whatToRemove = $(event.target).data('action');
|
||||
var del_files, value;
|
||||
|
||||
|
||||
// Purge failed
|
||||
if(whatToRemove == 'history-purge-failed') {
|
||||
del_files = 0;
|
||||
@@ -332,8 +334,8 @@ function HistoryModel(parent, data) {
|
||||
return;
|
||||
case 'category':
|
||||
// Exception for *
|
||||
if(self.historyStatus.category() == "*")
|
||||
return glitterTranslate.defaultText
|
||||
if(self.historyStatus.category() == "*")
|
||||
return glitterTranslate.defaultText
|
||||
return self.historyStatus.category();
|
||||
case 'size':
|
||||
return self.historyStatus.size();
|
||||
@@ -358,7 +360,7 @@ function HistoryModel(parent, data) {
|
||||
self.updateAllHistoryInfo = function(data, event) {
|
||||
// Show
|
||||
self.hasDropdown(true);
|
||||
|
||||
|
||||
// Update all info
|
||||
self.updateAllHistory = true;
|
||||
parent.parent.refresh(true);
|
||||
@@ -366,7 +368,7 @@ function HistoryModel(parent, data) {
|
||||
// Try to keep open
|
||||
keepOpen(event.target)
|
||||
}
|
||||
|
||||
|
||||
// Use KO-afterRender to add the click-functionality always
|
||||
self.addHistoryStatusStuff = function(item) {
|
||||
$(item).find('.history-status-modallink a').click(function(e) {
|
||||
@@ -382,7 +384,7 @@ function HistoryModel(parent, data) {
|
||||
$('#history-script-log .modal-title').text($(this).find("h3").text())
|
||||
$(this).find("h3, title").remove()
|
||||
$('#history-script-log').modal('show');
|
||||
});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
})
|
||||
@@ -392,7 +394,7 @@ function HistoryModel(parent, data) {
|
||||
self.deleteSlot = function(item, event) {
|
||||
// Are we not still processing?
|
||||
if(item.processingDownload() || item.processingWaiting()) return false;
|
||||
|
||||
|
||||
// Confirm?
|
||||
if(!self.parent.parent.confirmDeleteHistory() || confirm(glitterTranslate.removeDow1)) {
|
||||
callAPI({
|
||||
@@ -493,12 +495,12 @@ function HistoryModel(parent, data) {
|
||||
} else {
|
||||
submitUserReport(userDetail)
|
||||
}
|
||||
|
||||
|
||||
// After all, close it
|
||||
form.reset();
|
||||
$(form).parent().parent().dropdown('toggle');
|
||||
alert(glitterTranslate.sendThanks)
|
||||
|
||||
|
||||
function submitUserReport(theDetail) {
|
||||
// Send note
|
||||
callAPI({
|
||||
|
||||
2122
po/main/da.po
2122
po/main/da.po
File diff suppressed because it is too large
Load Diff
2172
po/main/de.po
2172
po/main/de.po
File diff suppressed because it is too large
Load Diff
@@ -161,4 +161,22 @@ msgstr ""
|
||||
"This key provides identity to indexer. Check "
|
||||
"your profile on the indexer's website."
|
||||
|
||||
#: sabnzbd/assembler.py:117 [Warning message]
|
||||
msgid "WARNING: Paused job \"%s\" because of encrypted RAR file"
|
||||
msgstr "WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords were tried)"
|
||||
|
||||
#: sabnzbd/skintext.py:333
|
||||
msgid "If empty, the standard port will only listen to HTTPS."
|
||||
msgstr "If empty, the SABnzbd Port set above will only listen to HTTPS."
|
||||
|
||||
#: sabnzbd/skintext.py:439
|
||||
msgid "Detect identical episodes in series (based on \"name/season/episode\")"
|
||||
msgstr "Detect identical episodes in series (based on \"name/season/episode\" of items in your History)"
|
||||
|
||||
#: sabnzbd/skintext.py:436
|
||||
msgid "Detect identical NZB files (based on NZB content)"
|
||||
msgstr "Detect identical NZB files (based on items in your History or files in .nzb Backup Folder)"
|
||||
|
||||
#: sabnzbd/assembler.py:120 [Warning message]
|
||||
msgid "WARNING: Aborted job \"%s\" because of encrypted RAR file"
|
||||
msgstr "WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords were tried)"
|
||||
|
||||
2210
po/main/es.po
2210
po/main/es.po
File diff suppressed because it is too large
Load Diff
2128
po/main/fi.po
2128
po/main/fi.po
File diff suppressed because it is too large
Load Diff
2126
po/main/fr.po
2126
po/main/fr.po
File diff suppressed because it is too large
Load Diff
2123
po/main/nb.po
2123
po/main/nb.po
File diff suppressed because it is too large
Load Diff
2125
po/main/nl.po
2125
po/main/nl.po
File diff suppressed because it is too large
Load Diff
2122
po/main/pl.po
2122
po/main/pl.po
File diff suppressed because it is too large
Load Diff
2125
po/main/pt_BR.po
2125
po/main/pt_BR.po
File diff suppressed because it is too large
Load Diff
2131
po/main/ro.po
2131
po/main/ro.po
File diff suppressed because it is too large
Load Diff
2114
po/main/ru.po
2114
po/main/ru.po
File diff suppressed because it is too large
Load Diff
2122
po/main/sr.po
2122
po/main/sr.po
File diff suppressed because it is too large
Load Diff
2122
po/main/sv.po
2122
po/main/sv.po
File diff suppressed because it is too large
Load Diff
2137
po/main/zh_CN.po
2137
po/main/zh_CN.po
File diff suppressed because it is too large
Load Diff
@@ -724,7 +724,7 @@ def _api_rss_now(name, output, kwargs):
|
||||
|
||||
def _api_retry_all(name, output, kwargs):
|
||||
""" API: Retry all failed items in History """
|
||||
return report(output, keyword='status', data=retry_all_jobs)
|
||||
return report(output, keyword='status', data=retry_all_jobs())
|
||||
|
||||
|
||||
def _api_reset_quota(name, output, kwargs):
|
||||
|
||||
@@ -397,7 +397,7 @@ class HistoryDB(object):
|
||||
pp = items.get('pp')
|
||||
script = items.get('script')
|
||||
cat = items.get('category')
|
||||
except AttributeError:
|
||||
except (AttributeError, IndexError):
|
||||
return '', '', '', '', ''
|
||||
return dtype, url, pp, script, cat
|
||||
|
||||
|
||||
@@ -44,22 +44,25 @@ def errormsg(msg):
|
||||
def send(message, email_to, test=None):
|
||||
""" Send message if message non-empty and email-parms are set """
|
||||
|
||||
def utf8(p):
|
||||
return p.encode('utf8', 'ignore')
|
||||
|
||||
# we should not use CFG if we are testing. we should use values
|
||||
# from UI instead.
|
||||
|
||||
if test:
|
||||
email_server = test.get('email_server')
|
||||
email_from = test.get('email_from')
|
||||
email_account = test.get('email_account')
|
||||
email_pwd = test.get('email_pwd')
|
||||
email_server = utf8(test.get('email_server'))
|
||||
email_from = utf8(test.get('email_from'))
|
||||
email_account = utf8(test.get('email_account'))
|
||||
email_pwd = utf8(test.get('email_pwd'))
|
||||
if email_pwd and not email_pwd.replace('*', ''):
|
||||
# If all stars, get stored password instead
|
||||
email_pwd = cfg.email_pwd()
|
||||
email_pwd = utf8(cfg.email_pwd())
|
||||
else:
|
||||
email_server = cfg.email_server()
|
||||
email_from = cfg.email_from()
|
||||
email_account = cfg.email_account()
|
||||
email_pwd = cfg.email_pwd()
|
||||
email_server = utf8(cfg.email_server())
|
||||
email_from = utf8(cfg.email_from())
|
||||
email_account = utf8(cfg.email_account())
|
||||
email_pwd = utf8(cfg.email_pwd())
|
||||
|
||||
# email_to is replaced at send_with_template, since it can be an array
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ def find_programs(curdir):
|
||||
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, 'win/par2/par2.exe')
|
||||
if not sabnzbd.newsunpack.RAR_COMMAND:
|
||||
sabnzbd.newsunpack.RAR_COMMAND = check(curdir, 'win/unrar/UnRAR.exe')
|
||||
sabnzbd.newsunpack.PAR2C_COMMAND = check(curdir, 'win/par2/par2-classic.exe')
|
||||
sabnzbd.newsunpack.PAR2C_COMMAND = check(curdir, 'win/par2/par2cmdline.exe')
|
||||
sabnzbd.newsunpack.ZIP_COMMAND = check(curdir, 'win/unzip/unzip.exe')
|
||||
sabnzbd.newsunpack.SEVEN_COMMAND = check(curdir, 'win/7zip/7za.exe')
|
||||
else:
|
||||
@@ -156,7 +156,7 @@ def external_processing(extern_proc, complete_dir, filename, nicename, cat, grou
|
||||
str(nicename), '', str(cat), str(group), str(status)]
|
||||
|
||||
if failure_url:
|
||||
command.extend(str(failure_url))
|
||||
command.append(str(failure_url))
|
||||
|
||||
if extern_proc.endswith('.py') and (sabnzbd.WIN32 or not os.access(extern_proc, os.X_OK)):
|
||||
command.insert(0, 'python')
|
||||
@@ -886,7 +886,7 @@ def ZIP_Extract(zipfile, extraction_path, one_folder):
|
||||
'-d%s' % extraction_path]
|
||||
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
|
||||
logging.debug('Starting unzip: %s', command)
|
||||
p = subprocess.Popen(command, shell=need_shell, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
startupinfo=stup, creationflags=creationflags)
|
||||
@@ -1025,7 +1025,7 @@ def seven_extract_core(sevenset, extensions, extraction_path, one_folder, delete
|
||||
'-o%s' % extraction_path, name]
|
||||
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
|
||||
logging.debug('Starting 7za: %s', command)
|
||||
p = subprocess.Popen(command, shell=need_shell, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
startupinfo=stup, creationflags=creationflags)
|
||||
@@ -1063,16 +1063,17 @@ def par2_repair(parfile_nzf, nzo, workdir, setname, single):
|
||||
assert isinstance(nzo, sabnzbd.nzbstuff.NzbObject)
|
||||
|
||||
# Check if file exists, otherwise see if another is done
|
||||
parfile = os.path.join(workdir, parfile_nzf.filename)
|
||||
if not os.path.exists(parfile) and parfile_nzf.extrapars:
|
||||
parfile_path = os.path.join(workdir, parfile_nzf.filename)
|
||||
if not os.path.exists(parfile_path) and parfile_nzf.extrapars:
|
||||
for new_par in parfile_nzf.extrapars:
|
||||
test_parfile = os.path.join(workdir, new_par.filename)
|
||||
if os.path.exists(test_parfile):
|
||||
parfile = test_parfile
|
||||
parfile_nzf = new_par
|
||||
break
|
||||
|
||||
parfile = short_path(parfile)
|
||||
|
||||
# Shorten just the workdir on Windows
|
||||
workdir = short_path(workdir)
|
||||
parfile = os.path.join(workdir, parfile_nzf.filename)
|
||||
|
||||
old_dir_content = os.listdir(workdir)
|
||||
used_joinables = ()
|
||||
@@ -1179,11 +1180,13 @@ def par2_repair(parfile_nzf, nzo, workdir, setname, single):
|
||||
logging.warning(T('Deleting %s failed!'), parfile)
|
||||
|
||||
deletables = []
|
||||
for f in pars:
|
||||
if f in setpars:
|
||||
deletables.append(os.path.join(workdir, f))
|
||||
deletables.extend(used_joinables)
|
||||
deletables.extend(used_par2)
|
||||
|
||||
# Delete pars of the set and maybe extra ones that par2 found
|
||||
deletables.extend([os.path.join(workdir, f) for f in setpars])
|
||||
deletables.extend([os.path.join(workdir, f) for f in pars])
|
||||
|
||||
for filepath in deletables:
|
||||
if filepath in joinables:
|
||||
joinables.remove(filepath)
|
||||
@@ -1237,10 +1240,20 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False, sin
|
||||
command = [str(PAR2C_COMMAND), cmd, parfile]
|
||||
else:
|
||||
command = [str(PAR2_COMMAND), cmd, parfile]
|
||||
|
||||
# Allow options if not classic or when classic and non-classic are the same
|
||||
if options and (not classic or (PAR2_COMMAND == PAR2C_COMMAND)):
|
||||
if (not classic or (PAR2_COMMAND == PAR2C_COMMAND)):
|
||||
command.insert(2, options)
|
||||
logging.debug('Par2-classic = %s', classic)
|
||||
|
||||
logging.debug('Par2-classic/cmdline = %s', classic)
|
||||
|
||||
# We need to check for the bad par2cmdline that skips blocks
|
||||
# Only if we're not doing multicore and user hasn't set options
|
||||
if not tbb and not options:
|
||||
par2text = run_simple([command[0], '-h'])
|
||||
if 'No data skipping' in par2text:
|
||||
logging.info('Detected par2cmdline version that skips blocks, adding -N parameter')
|
||||
command.insert(2, '-N')
|
||||
|
||||
# Append the wildcard for this set
|
||||
parfolder = os.path.split(parfile)[0]
|
||||
@@ -1572,7 +1585,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False, sin
|
||||
used_joinables.extend(reconstructed)
|
||||
|
||||
if retry_classic:
|
||||
logging.debug('Retry PAR2-joining with par2-classic')
|
||||
logging.debug('Retry PAR2-joining with par2-classic/cmdline')
|
||||
return PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=True, single=single)
|
||||
else:
|
||||
return finished, readd, pars, datafiles, used_joinables, used_par2
|
||||
@@ -1667,7 +1680,7 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
|
||||
""" Build filelists, if workdir_complete has files, ignore workdir.
|
||||
Optionally test content to establish RAR-ness
|
||||
"""
|
||||
joinables, zips, rars, sevens, filelist = ([], [], [], [], [])
|
||||
sevens, joinables, zips, rars, ts, filelist = ([], [], [], [], [], [])
|
||||
|
||||
if workdir_complete:
|
||||
for root, dirs, files in os.walk(workdir_complete):
|
||||
@@ -1691,19 +1704,28 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
|
||||
# Just skip failing names
|
||||
pass
|
||||
|
||||
sevens = [f for f in filelist if SEVENZIP_RE.search(f)]
|
||||
sevens.extend([f for f in filelist if SEVENMULTI_RE.search(f)])
|
||||
for file in filelist:
|
||||
# Extra check for rar (takes CPU/disk)
|
||||
file_is_rar = False
|
||||
if check_rar:
|
||||
file_is_rar = is_rarfile(file)
|
||||
|
||||
if check_rar:
|
||||
joinables = [f for f in filelist if f not in sevens and SPLITFILE_RE.search(f) and not is_rarfile(f)]
|
||||
else:
|
||||
joinables = [f for f in filelist if f not in sevens and SPLITFILE_RE.search(f)]
|
||||
|
||||
zips = [f for f in filelist if ZIP_RE.search(f)]
|
||||
|
||||
rars = [f for f in filelist if RAR_RE.search(f)]
|
||||
|
||||
ts = [f for f in filelist if TS_RE.search(f) and f not in joinables and f not in sevens]
|
||||
# Run through all the checks
|
||||
if SEVENZIP_RE.search(file) or SEVENMULTI_RE.search(file):
|
||||
# 7zip
|
||||
sevens.append(file)
|
||||
elif SPLITFILE_RE.search(file) and not file_is_rar:
|
||||
# Joinables, optional with RAR check
|
||||
joinables.append(file)
|
||||
elif ZIP_RE.search(file):
|
||||
# ZIP files
|
||||
zips.append(file)
|
||||
elif RAR_RE.search(file):
|
||||
# RAR files
|
||||
rars.append(file)
|
||||
elif TS_RE.search(file):
|
||||
# TS split files
|
||||
ts.append(file)
|
||||
|
||||
logging.debug("build_filelists(): joinables: %s", joinables)
|
||||
logging.debug("build_filelists(): zips: %s", zips)
|
||||
@@ -1727,7 +1749,7 @@ def QuickCheck(set, nzo):
|
||||
for file in md5pack:
|
||||
found = False
|
||||
for nzf in nzf_list:
|
||||
if file == nzf.filename:
|
||||
if platform_encode(file) == nzf.filename:
|
||||
found = True
|
||||
if (nzf.md5sum is not None) and nzf.md5sum == md5pack[file]:
|
||||
logging.debug('Quick-check of file %s OK', file)
|
||||
@@ -1985,7 +2007,7 @@ class SevenZip(object):
|
||||
|
||||
def run_simple(cmd):
|
||||
""" Run simple external command and return output """
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
txt = p.stdout.read()
|
||||
p.wait()
|
||||
return txt
|
||||
|
||||
@@ -144,7 +144,7 @@ class NzbQueue(TryList):
|
||||
registered = [nzo.work_name for nzo in self.__nzo_list]
|
||||
|
||||
# Retryable folders from History
|
||||
items = build_history()[0]
|
||||
items = build_history(output=True)[0]
|
||||
# Anything waiting or active or retryable is a known item
|
||||
registered.extend([platform_encode(os.path.basename(item['path']))
|
||||
for item in items if item['retry'] or item['loaded'] or item['status'] == Status.QUEUED])
|
||||
|
||||
@@ -64,7 +64,7 @@ __all__ = ['Article', 'NzbFile', 'NzbObject']
|
||||
RE_NORMAL = re.compile(r"(.+)(\.nzb)", re.I)
|
||||
SUBJECT_FN_MATCHER = re.compile(r'"([^"]*)"')
|
||||
RE_SAMPLE = re.compile(sample_match, re.I)
|
||||
PROBABLY_PAR2_RE = re.compile(r'(.*)\.vol(\d*)\+(\d*)\.par2', re.I)
|
||||
PROBABLY_PAR2_RE = re.compile(r'(.*)\.vol(\d*)[\+\-](\d*)\.par2', re.I)
|
||||
REJECT_PAR2_RE = re.compile(r'\.par2\.\d+', re.I) # Reject duplicate par2 files
|
||||
RE_NORMAL_NAME = re.compile(r'\.\w{2,5}$') # Test reasonably sized extension at the end
|
||||
|
||||
|
||||
@@ -247,6 +247,7 @@ SKIN_TEXT = {
|
||||
'purgeFailed' : TT('Purge Failed NZBs'), #: Button to delete all failed jobs in History
|
||||
'purgeFailed-Files' : TT('Purge Failed NZBs & Delete Files'), #: Button to delete all failed jobs in History, including files
|
||||
'purgeCompl' : TT('Purge Completed NZBs'), #: Button to delete all completed jobs in History
|
||||
'purgePage' : TT('Purge NZBs on the current page'), #: Button to delete jobs on current page in History
|
||||
'opt-extra-NZB' : TT('Optional Supplemental NZB'), #: Button to add NZB to failed job in History
|
||||
'msg-path' : TT('Path'), #: Path as displayed in History details
|
||||
'link-retryAll' : TT('Retry all failed'), #: Retry all failed jobs in History
|
||||
@@ -344,7 +345,6 @@ SKIN_TEXT = {
|
||||
'opt-rss_rate' : TT('RSS Checking Interval'),
|
||||
'explain-rss_rate' : TT('Checking interval (in minutes, at least 15). Not active when you use the Scheduler!'),
|
||||
'opt-bandwidth_max' : TT('Maximum line speed'),
|
||||
'explain-bandwidth_max' : TT('Highest possible linespeed in Bytes/second, e.g. 2M.'),
|
||||
'opt-bandwidth_perc' : TT('Percentage of line speed'),
|
||||
'explain-bandwidth_perc' : TT('Which percentage of the linespeed should SABnzbd use, e.g. 50'),
|
||||
'opt-cache_limitstr' : TT('Article Cache Limit'),
|
||||
@@ -972,7 +972,6 @@ SKIN_TEXT = {
|
||||
'wizard-goto' : TT('Go to SABnzbd'), #: Wizard step
|
||||
'wizard-exit' : TT('Exit SABnzbd'), #: Wizard EXIT button on first page
|
||||
'wizard-start' : TT('Start Wizard'), #: Wizard START button on first page
|
||||
'wizard-bandwidth-explain' : TT('When your ISP speed is 10 Mbits/sec, enter here 1M'), #: Wizard explain relation bits/sec bytes/sec
|
||||
|
||||
#Special
|
||||
'yourRights' : TT('''
|
||||
|
||||
@@ -207,7 +207,7 @@ class SeriesSorter(object):
|
||||
# First check if the show matches TV episode regular expressions. Returns regex match object
|
||||
self.match_obj, self.extras = check_regexs(self.original_dirname, series_match)
|
||||
if self.match_obj:
|
||||
logging.debug("Found TV Show - Starting folder sort (%s)", self.original_dirname)
|
||||
logging.debug("Found TV Show (%s)", self.original_dirname)
|
||||
self.matched = True
|
||||
|
||||
def is_match(self):
|
||||
@@ -524,7 +524,7 @@ class GenericSorter(object):
|
||||
if force or (cfg.enable_movie_sorting() and self.sort_string):
|
||||
# First check if the show matches TV episode regular expressions. Returns regex match object
|
||||
if force or (self.cat and self.cat.lower() in self.cats) or (not self.cat and 'None' in self.cats):
|
||||
logging.debug("Movie Sorting - Starting folder sort (%s)", self.original_dirname)
|
||||
logging.debug("Found Movie (%s)", self.original_dirname)
|
||||
self.matched = True
|
||||
|
||||
def get_final_path(self):
|
||||
@@ -728,7 +728,7 @@ class DateSorter(object):
|
||||
if force or (self.cat and self.cat.lower() in self.cats) or (not self.cat and 'None' in self.cats):
|
||||
self.match_obj, self.date_type = check_for_date(self.original_dirname, date_match)
|
||||
if self.match_obj:
|
||||
logging.debug("Date Sorting - Starting folder sort (%s)", self.original_dirname)
|
||||
logging.debug("Found date for sorting (%s)", self.original_dirname)
|
||||
self.matched = True
|
||||
|
||||
def is_match(self):
|
||||
|
||||
0
win/par2/COPYING → win/par2/GPL2.txt
Executable file → Normal file
0
win/par2/COPYING → win/par2/GPL2.txt
Executable file → Normal file
336
win/par2/README_par2cmdline.txt
Normal file
336
win/par2/README_par2cmdline.txt
Normal file
@@ -0,0 +1,336 @@
|
||||
par2cmdline is a PAR 2.0 compatible file verification and repair tool.
|
||||
|
||||
To see the ongoing development see
|
||||
https://github.com/BlackIkeEagle/par2cmdline
|
||||
|
||||
The original development was done on Sourceforge but stalled.
|
||||
For more information from the original authors see
|
||||
http://parchive.sourceforge.net
|
||||
Also for details of the PAR 2.0 specification and discussion of all
|
||||
things PAR.
|
||||
|
||||
WHAT EXACTLY IS PAR2CMDLINE?
|
||||
|
||||
par2cmdline is a program for creating and using PAR2 files to detect
|
||||
damage in data files and repair them if necessary. It can be used with
|
||||
any kind of file.
|
||||
|
||||
WHY IS PAR 2.0 better than PAR 1.0?
|
||||
|
||||
* It is not necessary to split a single large file into many equally
|
||||
size small files (although you can still do so if you wish).
|
||||
|
||||
* There is no loss of efficiency when operating on multiple files
|
||||
of different sizes.
|
||||
|
||||
* It is possible to repair damaged files (using exactly the amount of
|
||||
recovery data that corresponds to the amount of damage), rather than
|
||||
requiring the complete reconstruction of the damaged file.
|
||||
|
||||
* Recovery files may be of different sizes making it possible to
|
||||
obtain exactly the amount of recovery data required to carry out
|
||||
a repair.
|
||||
|
||||
* Because damaged data files are still useable during the recovery
|
||||
process, less recovery data is required to achieve a successful
|
||||
repair. It is therefore not necessary to create as much recovery
|
||||
data in the first place to achieve the same level of protection.
|
||||
|
||||
* You can protect up to 32768 files rather than the 256 that PAR 1.0
|
||||
is limited to.
|
||||
|
||||
* Damaged or incomplete recovery files can also be used during the
|
||||
recovery process in the same way that damaged data files can.
|
||||
|
||||
* PAR 2.0 requires less recovery data to provide the same level of
|
||||
protection from damage compared with PAR 1.0.
|
||||
|
||||
DOES PAR 2.0 HAVE ANY DISADVANTAGES?
|
||||
|
||||
Yes, there is one disadvantage:
|
||||
|
||||
* All PAR 2.0 program will take somewhat longer to create recovery
|
||||
files than a PAR 1.0 program does.
|
||||
|
||||
This disadvantage is considerably mitigated by the fact that you don't
|
||||
need to create as much recovery data in the first place to provide the
|
||||
same level of protection against loss and damage.
|
||||
|
||||
COMPILING PAR2CMDLINE
|
||||
|
||||
You should have received par2cmdline in the form of source code which
|
||||
you can compile on your computer. You may optionally have received a
|
||||
precompiled version of the program for your operating system.
|
||||
|
||||
If you have only downloaded a precompiled executable, then the source
|
||||
code should be available from the same location where you downloaded the
|
||||
executable from.
|
||||
|
||||
If you have MS Visual Studio .NET, then just open the par2cmdline.sln
|
||||
file and compile. You should then copy par2cmdline.exe to an appropriate
|
||||
location that is on your path.
|
||||
|
||||
To compile on Linux and other Unix variants use the following commands:
|
||||
|
||||
aclocal
|
||||
automake --add-missing
|
||||
autoconf
|
||||
./configure
|
||||
make
|
||||
make check
|
||||
make install
|
||||
|
||||
See INSTALL for full details on how to use the "configure" script.
|
||||
|
||||
USING PAR2CMDLINE
|
||||
|
||||
The command line parameters for par2cmdline are as follow:
|
||||
|
||||
par2 -h : show this help
|
||||
par2 -V : show version
|
||||
par2 -VV : show version and copyright
|
||||
|
||||
par2 c(reate) [options] <par2 file> [files]
|
||||
par2 v(erify) [options] <par2 file> [files]
|
||||
par2 r(epair) [options] <par2 file> [files]
|
||||
|
||||
Also:
|
||||
|
||||
par2create [options] <par2 file> [files]
|
||||
par2verify [options] <par2 file> [files]
|
||||
par2repair [options] <par2 file> [files]
|
||||
|
||||
Options:
|
||||
|
||||
-a<file> : Set the main par2 archive name
|
||||
required on create, optional for verify and repair
|
||||
-b<n> : Set the Block-Count
|
||||
-s<n> : Set the Block-Size (Don't use both -b and -s)
|
||||
-r<n> : Level of Redundancy (%)
|
||||
-r<c><n> : Redundancy target size, <c>=g(iga),m(ega),k(ilo) bytes
|
||||
-c<n> : Recovery block count (don't use both -r and -c)
|
||||
-f<n> : First Recovery-Block-Number
|
||||
-u : Uniform recovery file sizes
|
||||
-l : Limit size of recovery files (Don't use both -u and -l)
|
||||
-n<n> : Number of recovery files (Don't use both -n and -l)
|
||||
-m<n> : Memory (in MB) to use
|
||||
-v [-v] : Be more verbose
|
||||
-q [-q] : Be more quiet (-qq gives silence)
|
||||
-p : Purge backup files and par files on successful recovery or
|
||||
when no recovery is needed
|
||||
-R : Recurse into subdirectories (only useful on create)
|
||||
-N : No data skipping (find badly misspositioned data blocks)
|
||||
-S<n> : Skip leaway (distance +/- from expected block position)
|
||||
-- : Treat all remaining CommandLine as filenames
|
||||
|
||||
If you wish to create par2 files for a single source file, you may leave
|
||||
out the name of the par2 file from the command line. par2cmdline will then
|
||||
assume that you wish to base the filenames for the par2 files on the name
|
||||
of the source file.
|
||||
|
||||
You may also leave off the .par2 file extension when verifying and repairing.
|
||||
|
||||
CREATING PAR2 FILES
|
||||
|
||||
With PAR 2.0 you can create PAR2 recovery files for as few as 1 or as many as
|
||||
32768 files. If you wanted to create PAR1 recovery files for a single file
|
||||
you were forced to split the file into muliple parts and RAR was frequently
|
||||
used for this purpose. You do NOT need to split files with PAR 2.0.
|
||||
|
||||
To create PAR 2 recovery files for a single data file (e.g. one called
|
||||
test.mpg), you can use the following command:
|
||||
|
||||
par2 create test.mpg.par2 test.mpg
|
||||
|
||||
If test.mpg is an 800 MB file, then this will create a total of 8 PAR2 files
|
||||
with the following filenames (taking roughly 6 minutes on a PC with a
|
||||
1500MHz CPU):
|
||||
|
||||
test.mpg.par2 - This is an index file for verification only
|
||||
test.mpg.vol00+01.par2 - Recovery file with 1 recovery block
|
||||
test.mpg.vol01+02.par2 - Recovery file with 2 recovery blocks
|
||||
test.mpg.vol03+04.par2 - Recovery file with 4 recovery blocks
|
||||
test.mpg.vol07+08.par2 - Recovery file with 8 recovery blocks
|
||||
test.mpg.vol15+16.par2 - Recovery file with 16 recovery blocks
|
||||
test.mpg.vol31+32.par2 - Recovery file with 32 recovery blocks
|
||||
test.mpg.vol63+37.par2 - Recovery file with 37 recovery blocks
|
||||
|
||||
The test.mpg.par2 file is 39 KB in size and the other files vary in size from
|
||||
443 KB to 15 MB.
|
||||
|
||||
These par2 files will enable the recovery of up to 100 errors totalling 40 MB
|
||||
of lost or damaged data from the original test.mpg file when it and the par2
|
||||
files are posted on UseNet.
|
||||
|
||||
When posting on UseNet it is recommended that you use the "-s" option to set
|
||||
a blocksize that is equal to the Article size that you will use to post the
|
||||
data file. If you wanted to post the test.mpg file using an article size
|
||||
of 300 KB then the command you would type is:
|
||||
|
||||
par2 create -s307200 test.mpg.par2 test.mpg
|
||||
|
||||
This will create 9 PAR2 files instead of 8, and they will be capable of
|
||||
correcting up to 134 errors totalling 40 MB. It will take roughly 8 minutes
|
||||
to create the recovery files this time.
|
||||
|
||||
In both of these two examples, the total quantity of recovery data created
|
||||
was 40 MB (which is 5% of 800 MB). If you wish to create a greater or lesser
|
||||
quantity of recovery data, you can use the "-r" option.
|
||||
|
||||
To create 10% recovery data instead of the default of 5% and also to use a
|
||||
block size of 300 KB, you would use the following command:
|
||||
|
||||
par2 create -s307200 -r10 test.mpg.par2 test.mpg
|
||||
|
||||
This would also create 9 PAR2 files, but they would be able to correct up to
|
||||
269 errors totalling 80 MB. Since twice as much recovery data is created, it
|
||||
will take about 16 minutes to do so with a 1500MHz CPU.
|
||||
|
||||
The "-u" and "-n" options can be used to control exactly how many recovery
|
||||
files are created and how the recovery blocks are distributed among them.
|
||||
They do not affect the total quantity of recovery data created.
|
||||
|
||||
The "-f" option is used when you create additional recovery data e.g. If
|
||||
you have already created 10% and want another 5% then you migh use the
|
||||
following command:
|
||||
|
||||
par2 create -s307200 -r5 -f300 test.mpg.par2 test.mpg
|
||||
|
||||
This specifies the same block size (which is a requirement for additional
|
||||
recovery files), 5% recovery data, and a first block number of 300.
|
||||
|
||||
The "-m" option controls how much memory par2cmdline uses. It defaults to
|
||||
16 MB unless you override it.
|
||||
|
||||
When creating PAR2 recovery files you might want to fill up a "medium" like a
|
||||
DVD or a Blu-Ray. Therefore we can set the target size of the recovery files by
|
||||
issuing the following command:
|
||||
|
||||
par2 create -rm200 recovery.par2 *
|
||||
|
||||
It makes no sense to set a insanely high recovery size. The command will make
|
||||
that the resulting sum of the par2 files approaches the requested size. It is
|
||||
an estimate so don't go to crazy.
|
||||
|
||||
CREATING PAR2 FILES FOR MULTIPLE DATA FILES
|
||||
|
||||
When creating PAR2 recovery files from multiple data files, you must specify
|
||||
the base filename to use for the par2 files and the names of all of the data
|
||||
files.
|
||||
|
||||
If test.mpg had been split into multiple RAR files, then you could use:
|
||||
|
||||
par2 create test.mpg.rar.par2 test.mpg.part*.rar
|
||||
|
||||
The files filename "test.mpg.rar.par2" says what you want the par2 files to
|
||||
be called and "test.mpg.part*.rar" should select all of the RAR files.
|
||||
|
||||
VERIFYING AND REPAIRING
|
||||
|
||||
When using par2 recovery files to verify or repair the data files from
|
||||
which they were created, you only need to specify the filename of one
|
||||
of the par2 files to par2cmdline.
|
||||
|
||||
e.g.:
|
||||
|
||||
par2 verify test.mpg.par2
|
||||
|
||||
This tells par2cmdline to use the information in test.mpg.par2 to verify the
|
||||
data files.
|
||||
|
||||
par2cmdline will automatically search for the other par2 files that were
|
||||
created and use the information they contain to determine the filenames
|
||||
of the original data files and then to verify them.
|
||||
|
||||
If all of the data files are OK, then par2cmdline will report that repair
|
||||
will not be required.
|
||||
|
||||
If any of the data files are missing or damaged, par2cmdline will report
|
||||
the details of what it has found. If the recovery files contain enough
|
||||
recovery blocks to repair the damage, you will be told that repair is
|
||||
possible. Otherwise you will be told exactly how many recovery blocks
|
||||
will be required in order to repair.
|
||||
|
||||
To carry out a repair use the following command:
|
||||
|
||||
par2 repair test.mpg.par2
|
||||
|
||||
This tells par2cmdline to verify and if possible repair any damaged or
|
||||
missing files. If a repair is carried out, then each file which is
|
||||
repaired will be re-verified to confirm that the repair was successful.
|
||||
|
||||
MISNAMED AND INCOMPLETE DATA FILES
|
||||
|
||||
If any of the recovery files or data files have the wrong filename, then
|
||||
par2cmdline will not automatically find and scan them.
|
||||
|
||||
To have par2cmdline scan such files, you must include them on the command
|
||||
line when attempting to verify or repair.
|
||||
|
||||
e.g.:
|
||||
|
||||
par2 r test.mpg.par2 other.mpg
|
||||
|
||||
This tells par2cmdline to scan the file called other.mpg to see if it
|
||||
contains any data belonging to the original data files.
|
||||
|
||||
If one of the extra files specified in this way is an exact match
|
||||
for a data file, then the repair process will rename the file so that
|
||||
it has the correct filename.
|
||||
|
||||
Because par2cmdline is designed to be able to find good data within a
|
||||
damaged file, it can do the same with incomplete files downloaded from
|
||||
UseNet. If some of the articles for a file are missing, you should still
|
||||
download the file and save it to disk for par2cmdline to scan. If you
|
||||
do this then you may find that you can carry out a repair in a situation
|
||||
where you would not otherwise have sufficient recovery data.
|
||||
|
||||
You can have par2cmdline scan all files that are in the current directory
|
||||
using a command such as:
|
||||
|
||||
par2 r test.mpg.par2 *
|
||||
|
||||
WHAT TO DO WHEN YOU ARE TOLD YOU NEED MORE RECOVERY BLOCKS
|
||||
|
||||
If par2cmdline determines that any of the data files are damaged or
|
||||
missing and finds that there is insufficient recovery data to effect
|
||||
a repair, you will be told that you need a certain number of recovery
|
||||
blocks. You can obtain these by downloading additional recovery files.
|
||||
|
||||
In order to make things easy, par2 files have filenames that tell you
|
||||
exactly how many recovery blocks each one contains.
|
||||
|
||||
Assuming that the following command was used to create recovery data:
|
||||
|
||||
par2 c -b1000 -r5 test.mpg
|
||||
|
||||
Then the recovery files that are created would be called:
|
||||
|
||||
test.mpg.par2
|
||||
test.mpg.vol00+01.par2
|
||||
test.mpg.vol01+02.par2
|
||||
test.mpg.vol03+04.par2
|
||||
test.mpg.vol07+08.par2
|
||||
test.mpg.vol15+16.par2
|
||||
test.mpg.vol31+19.par2
|
||||
|
||||
The first file in this list does not contain any recovery data, it only
|
||||
contains information to verify the data files.
|
||||
|
||||
Each of the other files contains a different number of recovery blocks.
|
||||
The number after the '+' sign is the number of recovery blocks and the
|
||||
number preceding the '+' sign is the block number of the first recovery
|
||||
block in that file.
|
||||
|
||||
If par2cmdline told you that you needed 10 recovery blocks, then you would
|
||||
need "test.mpg.vol01+02.par2" and "test.mpg.vol07+08.par". You might of course
|
||||
choose to fetch "test.mpg.vol15+16.par2" instead (in which case you would have
|
||||
an extra 6 recovery blocks which would not be used for the repair).
|
||||
|
||||
REED SOLOMON CODING
|
||||
|
||||
PAR2 uses Reed Solomon Coding to perform its calculations. For details of this
|
||||
coding technique try the following link:
|
||||
|
||||
``A Tutorial on Reed-Solomon Coding for Fault-Tolerance in RAID-like Systems''
|
||||
<http://web.eecs.utk.edu/~plank/plank/papers/CS-96-332.html>
|
||||
Binary file not shown.
BIN
win/par2/par2cmdline.exe
Normal file
BIN
win/par2/par2cmdline.exe
Normal file
Binary file not shown.
0
win/par2/x64/COPYING → win/par2/x64/GPL2.txt
Executable file → Normal file
0
win/par2/x64/COPYING → win/par2/x64/GPL2.txt
Executable file → Normal file
Reference in New Issue
Block a user