Compare commits

...

34 Commits

Author SHA1 Message Date
shypike 527a03ee97 Fix typo 9 years ago
shypike 33a7874416 Update text files for 1.1.1 9 years ago
shypike 3ead1b56cd Updates translations 9 years ago
Safihre 2a59d5f54d Improve texts for External access settings 9 years ago
shypike cbea4e3fd7 Update text files for 1.1.1RC3 9 years ago
shypike 0c7e77ca65 8th parameter for user-script wasn't passed correctly. 9 years ago
Safihre f9b93fad93 par2cmdline on Windows also needs -N 9 years ago
Safihre fde8014acd Detect when par2cmdline needs -N 9 years ago
Safihre 692a3eaf45 CherryPy update long ago broke correct HTTPS port binding 9 years ago
Safihre 809b11c2f3 Only allow binding to IPv6 when ipv6_hosting enabled 9 years ago
Safihre f2ac2db86f Allow also "vol01-03.par" on top of "vol01+03.par" 9 years ago
Safihre 9dc6fa57ac Correct RegEx for par2-file detection 9 years ago
Safihre 9fa017989c Glitter didn't allow removal of a set job-password 9 years ago
Safihre 80c6626e58 Log the executed command for Unzip and 7Zip 9 years ago
Safihre b5d6078650 Improve HTTPS on Config->General and improve Duplicates text 9 years ago
Safihre a31673ac3e Unicode failed downloads are seen as Orphans due to xml_encoding 9 years ago
Safihre f18ec189b8 Only shorten pathname on Windows for par2 9 years ago
Safihre b3623bdd1f QuickCheck would fail unicode files 9 years ago
Safihre 8247451b28 Clean-up all par2 of a set 9 years ago
Safihre 009ee14b14 Fix retry_all API-call 9 years ago
Safihre 2d16c19f18 Avoid duplicates in build_filelists() that break 7zip support 9 years ago
Safihre 8f84cda308 Improve text on Delete-page button 9 years ago
Safihre 9397a02bfe Make sure we show results when less than 1 page 9 years ago
Safihre 6c9a26b05e Allow also "vol01-03.par" on top of "vol01+03.par" 9 years ago
shypike 01d668ac08 Update text files for 1.1.1RC2 9 years ago
Safihre 38a943dc12 Tiny change to Sorting debug logging 9 years ago
Safihre 9b8dbdb179 Change par2-classic to par2cmdline on Windows 9 years ago
Safihre 637c00dcff Avoid javascript error in Servers page 9 years ago
Safihre 1a6db92920 CSS fix for Config menu on mobile 9 years ago
Safihre 74c9444f5f "Max line speed" more clear, using a select 9 years ago
Safihre abb0226d5a Don't error out on Retry database errors 9 years ago
shypike 739921e423 Update .gitignore 9 years ago
shypike 57780c280c Fix emailer by converting account data to UTF-8 (#700) 9 years ago
shypike 0fbf79f5af Update text files for 1.1.1RC1 9 years ago
  1. 7
      .gitignore
  2. 2
      ABOUT.txt
  3. 3
      INSTALL.txt
  4. 2
      ISSUES.txt
  5. 4
      PKG-INFO
  6. 27
      README.mkd
  7. 12
      SABnzbd.py
  8. 47
      interfaces/Config/templates/config_general.tmpl
  9. 14
      interfaces/Config/templates/config_server.tmpl
  10. 10
      interfaces/Config/templates/staticcfg/css/style.css
  11. 2
      interfaces/Config/templates/staticcfg/js/script.js
  12. 26
      interfaces/Glitter/templates/include_overlays.tmpl
  13. 48
      interfaces/Glitter/templates/static/javascripts/glitter.filelist.pagination.js
  14. 42
      interfaces/Glitter/templates/static/javascripts/glitter.history.js
  15. 2122
      po/main/da.po
  16. 2168
      po/main/de.po
  17. 18
      po/main/en.po
  18. 2196
      po/main/es.po
  19. 2124
      po/main/fi.po
  20. 2122
      po/main/fr.po
  21. 2123
      po/main/nb.po
  22. 2125
      po/main/nl.po
  23. 2122
      po/main/pl.po
  24. 2121
      po/main/pt_BR.po
  25. 2127
      po/main/ro.po
  26. 2114
      po/main/ru.po
  27. 2122
      po/main/sr.po
  28. 2122
      po/main/sv.po
  29. 2135
      po/main/zh_CN.po
  30. 2
      sabnzbd/api.py
  31. 2
      sabnzbd/database.py
  32. 21
      sabnzbd/emailer.py
  33. 84
      sabnzbd/newsunpack.py
  34. 2
      sabnzbd/nzbqueue.py
  35. 2
      sabnzbd/nzbstuff.py
  36. 3
      sabnzbd/skintext.py
  37. 6
      sabnzbd/tvsort.py
  38. 0
      win/par2/GPL2.txt
  39. 0
      win/par2/README_par2.txt
  40. 336
      win/par2/README_par2cmdline.txt
  41. BIN
      win/par2/par2-classic.exe
  42. BIN
      win/par2/par2cmdline.exe
  43. 0
      win/par2/x64/GPL2.txt
  44. 0
      win/par2/x64/README_par2x64.txt

7
.gitignore

@ -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

2
ABOUT.txt

@ -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,

3
INSTALL.txt

@ -1,5 +1,4 @@
SABnzbd 1.1.0
SABnzbd 1.1.1
-------------------------------------------------------------------------------
0) LICENSE
-------------------------------------------------------------------------------

2
ISSUES.txt

@ -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

@ -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

27
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:
## What's new in 1.1.1
## Bug 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

@ -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:

47
interfaces/Config/templates/config_general.tmpl

@ -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') &amp; $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>

14
interfaces/Config/templates/config_server.tmpl

@ -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
**/

10
interfaces/Config/templates/staticcfg/css/style.css

@ -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;

2
interfaces/Config/templates/staticcfg/js/script.js

@ -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:';

26
interfaces/Glitter/templates/include_overlays.tmpl

@ -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>

48
interfaces/Glitter/templates/static/javascripts/glitter.filelist.pagination.js

@ -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)
self.currentStart(0);
// Are we on next page?
if(self.currentPage() > 1) {
// Force full update
parent.parent.refresh(true);
}
// Reset all to make sure we see something
// Move to current page
self.currentPage(1);
self.currentStart(0);
// 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())

42
interfaces/Glitter/templates/static/javascripts/glitter.history.js

@ -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 + '&hellip;')
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

File diff suppressed because it is too large

2168
po/main/de.po

File diff suppressed because it is too large

18
po/main/en.po

@ -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)"

2196
po/main/es.po

File diff suppressed because it is too large

2124
po/main/fi.po

File diff suppressed because it is too large

2122
po/main/fr.po

File diff suppressed because it is too large

2123
po/main/nb.po

File diff suppressed because it is too large

2125
po/main/nl.po

File diff suppressed because it is too large

2122
po/main/pl.po

File diff suppressed because it is too large

2121
po/main/pt_BR.po

File diff suppressed because it is too large

2127
po/main/ro.po

File diff suppressed because it is too large

2114
po/main/ru.po

File diff suppressed because it is too large

2122
po/main/sr.po

File diff suppressed because it is too large

2122
po/main/sv.po

File diff suppressed because it is too large

2135
po/main/zh_CN.po

File diff suppressed because it is too large

2
sabnzbd/api.py

@ -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):

2
sabnzbd/database.py

@ -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

21
sabnzbd/emailer.py

@ -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

84
sabnzbd/newsunpack.py

@ -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)])
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]
for file in filelist:
# Extra check for rar (takes CPU/disk)
file_is_rar = False
if check_rar:
file_is_rar = is_rarfile(file)
# 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

2
sabnzbd/nzbqueue.py

@ -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])

2
sabnzbd/nzbstuff.py

@ -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

3
sabnzbd/skintext.py

@ -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('''

6
sabnzbd/tvsort.py

@ -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

0
win/par2/README_FIRST.txt → win/par2/README_par2.txt

336
win/par2/README_par2cmdline.txt

@ -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>

BIN
win/par2/par2-classic.exe

Binary file not shown.

BIN
win/par2/par2cmdline.exe

Binary file not shown.

0
win/par2/x64/COPYING → win/par2/x64/GPL2.txt

0
win/par2/x64/README_FIRST.txt → win/par2/x64/README_par2x64.txt

Loading…
Cancel
Save