Compare commits

..

57 Commits

Author SHA1 Message Date
Safihre
9cbae891d0 WIP on release313 2024-10-16 10:37:53 +02:00
Safihre
fb0ef21768 The tests folder was ignored by default by Renovate 2024-10-15 11:55:26 +02:00
Safihre
277679ef53 Add Python 3.13 to CI tests 2024-10-15 10:10:24 +02:00
Safihre
e7e47bbcb0 Do not compare articles just based on article-ID
Turns out that there are NZBs that contain duplicate article-ID's within 1 file. This causes all "article in nzf.article" comparisons to return the wrong comparison.
2024-10-15 09:44:04 +02:00
Safihre
65ffb5ca81 All resets of try lists should be locked fully 2024-10-15 09:43:55 +02:00
SABnzbd Automation
6cf308e441 Update translatable texts
[skip ci]
2024-10-14 01:47:23 +00:00
renovate[bot]
870fa40c91 Update all dependencies 2024-10-14 01:46:42 +00:00
Safihre
39d9eaec2a Create new history database in case of no such table error 2024-10-08 21:06:59 +02:00
SABnzbd Automation
6fd4d0882c Update translatable texts
[skip ci]
2024-10-08 07:39:00 +00:00
Safihre
32591f7c46 Update text files for 4.4.0Alpha2 2024-10-08 09:38:01 +02:00
SABnzbd Automation
6b47d1126d Update translatable texts
[skip ci]
2024-10-07 00:23:20 +00:00
renovate[bot]
53df39dd12 Update dependency pywin32 to v307 2024-10-07 00:22:35 +00:00
Safihre
b5d33fc17c Mount interface again on both / and url_base
Closes #2936
2024-10-05 20:58:45 +02:00
SABnzbd Automation
8a517b668e Update translatable texts
[skip ci]
2024-10-01 06:27:56 +00:00
thezoggy
67135ba4c8 Update 7zip and multipar (#2942)
* Update 7zip to 24.08

* Update multipar to 1.3.3.3
2024-10-01 08:27:12 +02:00
renovate[bot]
c9efda1889 Update all dependencies 2024-09-30 00:44:59 +00:00
Safihre
250869c242 Force Selenium browser_version to 127
Closes #2932
2024-09-26 22:41:20 +02:00
SABnzbd Automation
844650e6be Update translatable texts
[skip ci]
2024-09-26 20:27:25 +00:00
Safihre
6685c72894 Saving Specials page would result in restart even for no changes
Relates to #2932
2024-09-26 22:26:39 +02:00
renovate[bot]
154a5e4989 Update all dependencies (#2937)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-23 07:34:11 +02:00
SABnzbd Automation
93d302c9d7 Update translatable texts
[skip ci]
2024-09-20 08:11:43 +00:00
Safihre
f664df7f05 Update text files for 4.4.1Alpha1 2024-09-20 10:10:55 +02:00
thezoggy
8fbf50292b Add additional server priority colors (#2934) 2024-09-17 20:27:22 +02:00
Safihre
f3fed43022 Remove logging line for scheduled history purge 2024-09-17 15:06:56 +02:00
Safihre
2d323ba18c Update to Python 3.12.6 and drop macOS <10.13 support
Closes #2784
2024-09-16 13:59:38 +02:00
Safihre
1ec30a56e1 Improve logging of server address retrieval 2024-09-16 13:58:57 +02:00
renovate[bot]
b98f3a07dd Update all dependencies (#2933)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-16 13:58:12 +02:00
Safihre
46170ffb3d Reset article queue only when really stopping the server
Closes #2866
2024-09-10 20:25:56 +03:00
renovate[bot]
5e8b41be5a Update all dependencies (#2930)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-10 20:13:14 +03:00
SABnzbd Automation
47a2d5387d Update translatable texts
[skip ci]
2024-09-10 07:57:26 +00:00
Safihre
1e61239933 Add Bootstrap tooltips to any elements with title 2024-09-10 10:55:06 +03:00
Safihre
aedbf35be8 Show file being moved during Moving phase 2024-09-07 11:10:58 +03:00
Safihre
cf9540842b Config restart would always try to build URL instead of using current
#2835
2024-09-07 10:51:11 +03:00
Safihre
9205b9161b Update Linux test runners 2024-09-05 12:43:31 +03:00
Safihre
07b64b4abb Update tests for change in webpage mounting 2024-09-04 12:09:07 +03:00
Safihre
c56145e424 Only mount webpages on /, no longer on /sabnzbd
Closes #2929
2024-09-04 11:39:38 +03:00
Safihre
ef11aba166 Add basic system to do trigger one-time config conversions 2024-09-04 11:39:37 +03:00
SABnzbd Automation
fcf03e9a59 Update translatable texts
[skip ci]
2024-09-03 09:06:42 +00:00
renovate[bot]
6662065bb1 Update all dependencies (#2928)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 12:05:52 +03:00
SABnzbd Automation
4973672892 Update translatable texts
[skip ci]
2024-08-30 12:47:50 +00:00
bt90
efa73a52e1 Add doctype to login page (#2927) 2024-08-30 14:47:02 +02:00
SABnzbd Automation
82098a6228 Update translatable texts
[skip ci]
2024-08-26 02:47:15 +00:00
renovate[bot]
07250aa355 Update all dependencies 2024-08-26 02:46:28 +00:00
Safihre
46caa8b33f Add Docker to CPU label 2024-08-19 20:21:19 +02:00
Safihre
b0564c1bab Small style fixes for the Config 2024-08-19 11:42:12 +02:00
SABnzbd Automation
3c8a85ff35 Update translatable texts
[skip ci]
2024-08-19 09:28:34 +00:00
Safihre
656c329912 Update version to 4.4.0-develop 2024-08-19 11:27:46 +02:00
SABnzbd Automation
983253908c Update translatable texts
[skip ci]
2024-08-19 01:20:50 +00:00
renovate[bot]
cef0eeb25b Update all dependencies 2024-08-19 01:20:10 +00:00
Safihre
a9eace759f increase_bad_articles_counter should be called before register_article 2024-08-16 22:23:14 +02:00
Safihre
ad0e7bf5df Prevent excessive newswrapper data buffer size
Closes #2895
2024-08-16 17:25:57 +02:00
Safihre
bea348232a Do not use article_queue when resetting newswrapper
Relates to #2866
2024-08-16 16:50:28 +02:00
jcfp
1519dbc554 Remove pyfakefs workarounds and put its new apply_umask option to good use (#2922) 2024-08-16 15:44:52 +02:00
SABnzbd Automation
297455cd35 Update translatable texts
[skip ci]
2024-08-12 02:04:47 +00:00
renovate[bot]
56b68024db Update all dependencies 2024-08-12 02:04:06 +00:00
SABnzbd Automation
09aa09a55b Update translatable texts
[skip ci]
2024-08-09 17:46:39 +00:00
Sander
f1d134fe2e Deobfuscate subtitles (#2903)
* deobfuscate_subtitles

* deobfuscate_subtitles: unit test aka pytest

* deobfuscate_subtitles: unit test aka pytest

* deobfuscate_subtitles: no reanem is first part of filename is the samen

* deobfuscate_subtitles: no reanem is first part of filename is the samen

* deobfuscate_subtitles: no reanem is first part of filename is the samen

* deobfuscate_subtitles: more structured unit test method

* deobfuscate_subtitles: back to basic testing method

* deobfuscate_subtitles: cleanup

* deobfuscate_subtitles: cleanup

* deobfuscate_filenames.test_first_file_is_much_bigger() improved

* deobfuscate_subtitles(): checks on biggest file and srt files. input can be directory or filelist.

* rename to clearly_one_biggest_file()

* WIP on develop

* accept work by safihre

* do nothing when not one_file_is_biggest

* a lot of cleanup, also with help of the walrus

* a lot of cleanup, also with help of the walrus

* fix typo's in test_deobfuscate_filenames.py

* Update sabnzbd/postproc.py

Co-authored-by: Safihre <safihre@sabnzbd.org>

* handle review comments

* handle review comments

* remove import glob

* remove special underscore support. Add srt deob info into GUI-history

---------

Co-authored-by: sander <san.d.erjonkers+github@gmail.com>
Co-authored-by: Safihre <safihre@sabnzbd.org>
2024-08-09 19:45:57 +02:00
100 changed files with 777 additions and 519 deletions

View File

@@ -15,6 +15,7 @@
"builder/release-requirements.txt"
]
},
"ignorePaths": [],
"ignoreDeps": [
"jaraco.text",
"jaraco.context",

View File

@@ -69,8 +69,8 @@ jobs:
# We need the official Python, because the GA ones only support newer macOS versions
# The deployment target is picked up by the Python build tools automatically
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
PYTHON_VERSION: "3.12.5"
MACOSX_DEPLOYMENT_TARGET: "10.9"
PYTHON_VERSION: "3.12.6"
MACOSX_DEPLOYMENT_TARGET: "10.13"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64
ARCHFLAGS: -arch x86_64 -arch arm64
@@ -95,14 +95,12 @@ jobs:
- name: Install Python
run: sudo installer -pkg ~/python.pkg -target /
- name: Install Python dependencies
# We have to manually take a few steps:
# 1. We need to build the PyInstaller bootloader:
# https://github.com/pyinstaller/pyinstaller/issues/6235
# We have to manually compile some modules as they don't automatically fetch universal2 binaries
run: |
python3 --version
pip3 install --upgrade pip wheel
pip3 install --upgrade -r requirements.txt --no-binary cffi,CT3,PyYAML,charset_normalizer --no-dependencies
PYINSTALLER_COMPILE_BOOTLOADER=1 pip3 install --upgrade -r builder/requirements.txt --no-binary pyinstaller --no-dependencies
pip3 install --upgrade -r builder/requirements.txt --no-dependencies
- name: Import macOS codesign certificates
# Taken from https://github.com/Apple-Actions/import-codesign-certs/pull/27 (comments)
env:

View File

@@ -31,18 +31,18 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-architecture: ["x64"]
name: ["Linux"]
os: [ubuntu-20.04]
os: [ubuntu-latest]
include:
- name: macOS
os: macos-latest
python-version: "3.12"
python-version: "3.13"
python-architecture: "x64"
- name: Windows
os: windows-latest
python-version: "3.12"
python-version: "3.13"
python-architecture: "x64"
- name: Windows (32bit)
os: windows-latest
@@ -60,7 +60,7 @@ jobs:
cache-dependency-path: "**/requirements.txt"
- name: Install system dependencies
if: runner.os == 'Linux'
run: sudo apt-get install unrar p7zip-full par2
run: sudo apt-get install unrar 7zip par2
- name: Install Python dependencies
run: |
python --version

View File

@@ -1,58 +1,17 @@
Release Notes - SABnzbd 4.3.3
Release Notes - SABnzbd 4.4.0 Alpha 2
=========================================================
This is the third bug fix release of SABnzbd 4.3.0.
This is the first test release of SABnzbd 4.4.0.
## Bug fixes and changes since 4.3.2
* Reduced chance of jobs getting stuck at 99%.
* Prevent crash in case of invalid articles.
* Correct handling of empty or `Default` category when adding a job.
* History API-output could contain inconsistent variable types.
* Skip external IPv6 check if only link local addresses are available.
* Shortened timeouts when resolving addresses during checks.
* Windows: Could not repair or extract on ARM platforms.
* Windows: Add file version information to installer.
## New features since 4.3.0
## Bug fixes and changes since 4.3.1
* Subtitle files will be deobfuscated if required.
* macOS: Dropped support for macOS 10.12 and below.
* Added Special option `disable_archive` for jobs to always be permanently deleted.
* Specific AppRise notifications could fail to send.
* Update of the article decoder core (`rapidyenc`).
* Windows: After some time the interface would no longer load.
* Windows: Custom shortcuts would be removed by the installer.
* Windows/macOS: Updated Unrar to 7.01 and 7zip to 24.05.
## Bug fixes since 4.3.0
## Key changes since 4.2.0
* **Archive:**
* When jobs are removed from the History, they are moved to the Archive.
* Keep your History clean and still be able to reliably use Duplicate Detection.
* **Apprise Integrated:**
* Send notifications using Apprise to almost any notification service.
* Supported notifications: https://github.com/caronc/apprise/wiki
* Notification Script `SABnzbd-notify.py` is no longer needed.
* **Added IPv6-staging option:**
* Enable `ipv6_staging` in Config - Specials to get additional IPv6 features:
* Add IPv6 hostnames during address selection.
* Internet Bandwidth is measured separately for IPv4 and IPv6.
* **Other:**
* The `text` output format is removed from the API, `json` is the default.
* Handling of multiple inputs to several API methods was improved.
* File browser dialog is available to select file paths in the Config.
* Users will be warned if they configure an Indexer as a Server.
* Added `SAB_API_KEY` and `SAB_API_URL` to script environment variables.
* Windows/macOS: Updated Python to 3.12.3, Multipar to v1.3.3.2,
Unrar to 7.00 and 7zip to 24.03.
## Bug fixes since 4.2.0
* Incorrect warnings of unknown status codes could be thrown.
* Watched Folder would not work if Socks5 proxy was active.
* Prevent crash on invalid Server Expiration Date.
* Windows: Installer could create duplicate shortcuts.
* Toggling of Servers could result in jobs being stuck at 99%.
* Config restart would always determine redirect URL instead of using current.
## Upgrade notices

View File

@@ -165,7 +165,7 @@ if sys.platform == "darwin":
"NSPersistentStoreTypeKey": "Binary",
}
],
"LSMinimumSystemVersion": "10.9",
"LSMinimumSystemVersion": "10.13",
"LSEnvironment": {"LANG": "en_US.UTF-8", "LC_ALL": "en_US.UTF-8"},
}

View File

@@ -1,2 +1,2 @@
PyGithub==2.3.0
PyGithub==2.4.0
praw==7.7.1

View File

@@ -5,19 +5,19 @@ packaging==24.1
pyinstaller-hooks-contrib==2024.8
altgraph==0.17.4
wrapt==1.16.0
setuptools==72.1.0
setuptools==75.1.0
# Required on 32bit Windows, exclude it based on Python-version
importlib_metadata==8.2.0; python_version < '3.10'
importlib_resources==6.4.0; python_version < '3.10'
zipp==3.20.0; python_version < '3.10'
importlib_metadata==8.5.0; python_version < '3.10'
importlib_resources==6.4.5; python_version < '3.10'
zipp==3.20.2; python_version < '3.10'
# orjson does not support 32bit Windows, also exclude based on Python-version
orjson==3.10.7; python_version > '3.8'
# For the Windows build
pefile==2023.2.7; sys_platform == 'win32'
pywin32-ctypes==0.2.2; sys_platform == 'win32'
pefile==2024.8.26; sys_platform == 'win32'
pywin32-ctypes==0.2.3; sys_platform == 'win32'
# For the macOS build
dmgbuild==1.6.2; sys_platform == 'darwin'

View File

@@ -92,7 +92,7 @@
<span class="icon-bar"></span>
</button>
<a class="navbar-logo navbar-logo-small" href="${root}" title="$T('Home')">
<a class="navbar-logo navbar-logo-small" href="${root}" title="$T('Home')" data-placement="bottom">
#include $webdir + "/staticcfg/images/logo-small.svg"#
</a>
</div>

View File

@@ -28,7 +28,7 @@
</label>
<div class="advanced-buttonSeperator"></div>
<div class="chart-selector-container" title="$T('selectedDates')">
<div class="chart-selector-container" title="$T('selectedDates')" data-placement="bottom">
<span class="glyphicon glyphicon-signal"></span>
<!--#set today = datetime.date.today()#-->
<input type="date" name="chart-start" id="chart-start" value="<!--#echo (today-datetime.timedelta(days=30)).strftime('%Y-%m-%d')#-->"> -
@@ -142,7 +142,7 @@
</div>
</div>
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
<!--#set $prio_colors = ["#59cc33", "#26a69a", "#3366cc", "#7f33cc", "#cc33a6", "#f39c12", "#cc3333", "#8d6e63"] #-->
<!--#set $cur_prio_color = -1 #-->
<!--#set $last_prio = -1 #-->
<!--#for $cur, $server in enumerate($servers) #-->

View File

@@ -71,7 +71,7 @@
<div class="field-pair">
<label class="config" for="field_sort_string_$cur">$T('sortString')</label>
<input type="text" name="sort_string" id="field_sort_string_$cur" value="$slot.sort_string" required="required" />
<button type="button" title="$T('sort-legenda')" class="btn btn-default patternKey" onclick="jQuery('#pattern_explainer_$cur').toggle(); window.scrollBy(0, 500);">
<button type="button" class="btn btn-default patternKey" onclick="jQuery('#pattern_explainer_$cur').toggle(); window.scrollBy(0, 500);">
<span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> $T('sort-legenda')
</button>
</div>

View File

@@ -1,3 +1,4 @@
<!DOCTYPE HTML>
<html lang="$active_lang">
<head>
<title>SABnzbd - $T('login')</title>

View File

@@ -262,9 +262,10 @@ function do_restart() {
// Show overlay
$('.main-restarting').show()
// What template
// Check if we need redirect
// Uses == on purpose, because val() returns string and data() returns int!
var switchedHTTPS = ($('#enable_https').is(':checked') === ($('#enable_https').data('original') === undefined))
var portsUnchanged = ($('#port').val() === $('#port').data('original')) && ($('#https_port').val() === $('#https_port').data('original'))
var portsUnchanged = ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))
// Are we on settings page or did nothing change?
if(!$('body').hasClass('General') || (!switchedHTTPS && portsUnchanged)) {
@@ -489,6 +490,9 @@ $(document).ready(function () {
addRowColor()
}
addRowColor()
// Add tooltips
jQuery('[title]').tooltip()
});
/*

View File

@@ -30,7 +30,7 @@
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="4.3.3" date="2024-08-20" type="stable"/>
<release version="4.3.3" date="2024-08-01" type="stable"/>
<release version="4.3.2" date="2024-05-30" type="stable"/>
<release version="4.3.1" date="2024-05-03" type="stable"/>
<release version="4.3.0" date="2024-05-01" type="stable"/>

View File

Binary file not shown.

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"

View File

@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: ION, 2020\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"

View File

@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
"MIME-Version: 1.0\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
@@ -399,6 +399,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
@@ -434,6 +434,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr "Přímé rozbalení"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
@@ -437,6 +437,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -14,12 +14,13 @@
# HandyDandy04, 2024
# Safihre <safihre@sabnzbd.org>, 2024
# Gjelbrim Haskaj, 2024
# Stefan Rodriguez Galeano, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Gjelbrim Haskaj, 2024\n"
"Last-Translator: Stefan Rodriguez Galeano, 2024\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -257,7 +258,7 @@ msgstr "Die Verbindung konnte nicht überprüft werden. (%s)"
#: sabnzbd/api.py
msgid "Resolving address"
msgstr "Adresse wird aufgelöst"
msgstr "Adresse wird aufgelöst"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -477,6 +478,10 @@ msgstr "Entschleiern korrigierte die Erweiterung von %d Datei(en)"
msgid "Deobfuscate renamed %d file(s)"
msgstr "Entschleiern hat %dDatei(en) umbenannt"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr "Umbenannte Untertiteldatei(en)%d verschleiern"
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr "Direkt entpacken"
@@ -557,7 +562,7 @@ msgstr "Schwerer Fehler im Downloader"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
msgstr "%s@%s:Unbekannter Statuscode%s für Artikel erhalten %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
@@ -1231,21 +1236,22 @@ msgstr "Prowl-Nachricht konnte nicht versendet werden"
#. Warning message
#: sabnzbd/notifier.py
msgid "Failed to send Apprise message - no URLs defined"
msgstr ""
msgstr "Übertragung der Info-Nachricht fehlgeschlagen - keine URLs definiert"
#. Warning message
#: sabnzbd/notifier.py
msgid "One or more Apprise URLs could not be loaded."
msgstr ""
msgstr "Eine oder mehrere Informations-URLs konnten nicht geladen werden."
#: sabnzbd/notifier.py
msgid "Failed to send one or more Apprise Notifications"
msgstr ""
"Eine oder mehrere Info-Benachrichtigungen konnten nicht gesendet werden"
#. Warning message
#: sabnzbd/notifier.py
msgid "Failed to send Apprise message"
msgstr ""
msgstr "Info-Nachricht konnte nicht gesendet werden"
#. Error message
#: sabnzbd/notifier.py
@@ -2430,7 +2436,7 @@ msgstr "Alle Elemente in der Warteschlange löschen?"
#. Delete confirmation popup
#: sabnzbd/skintext.py
msgid "Are you sure you want to remove these jobs?"
msgstr ""
msgstr "Sind Sie sicher, dass Sie diese Aufträge entfernen wollen?"
#. Queue page button
#: sabnzbd/skintext.py
@@ -2455,7 +2461,7 @@ msgstr "NZBs und Dateien löschen"
#. Checkbox if job should be added to Archive
#: sabnzbd/skintext.py
msgid "Permanently delete (skip archive)"
msgstr ""
msgstr "erhaft löschen (Archiv überspringen)"
#. Caption for missing articles in Queue
#: sabnzbd/skintext.py
@@ -2478,7 +2484,7 @@ msgstr "Kontingent jetzt zurücksetzen"
#: sabnzbd/skintext.py
msgid "Archive"
msgstr ""
msgstr "Archiv"
#. Button/link hiding History job details
#: sabnzbd/skintext.py
@@ -2503,7 +2509,7 @@ msgstr "Alle anzeigen"
#. Button showing all archived jobs
#: sabnzbd/skintext.py
msgid "Show Archive"
msgstr ""
msgstr "Zeige Archiv"
#. History table header - Size of the download quota
#: sabnzbd/skintext.py
@@ -2796,11 +2802,11 @@ msgstr "Port, auf dem SABnzbd auf Anfragen warten soll."
#: sabnzbd/skintext.py
msgid "Web Interface Theme"
msgstr ""
msgstr "Benutzeroberfläche"
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr ""
msgstr "Wählen Sie ein Theme."
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
@@ -2971,28 +2977,36 @@ msgstr "Alle Aufträge behalten"
msgid ""
"Move jobs to the archive if the history exceeds specified number of jobs"
msgstr ""
"Verschieben von Aufträgen in das Archiv, wenn der Verlauf die angegebene "
"Anzahl von Aufträgen überschreitet."
#: sabnzbd/skintext.py
msgid ""
"Delete jobs if the history and archive exceeds specified number of jobs"
msgstr ""
"Löschen von Aufträgen, wenn der Verlauf und das Archiv die angegebene Anzahl"
" von Aufträgen überschreiten"
#: sabnzbd/skintext.py
msgid "Move jobs to the archive after specified number of days"
msgstr ""
"Verschieben von Aufträgen in das Archiv nach einer bestimmten Anzahl von "
"Tagen"
#: sabnzbd/skintext.py
msgid ""
"Delete jobs from the history and archive after specified number of days"
msgstr ""
"Löschen von Aufträgen aus der Historie und dem Archiv nach einer bestimmten "
"Anzahl von Tagen"
#: sabnzbd/skintext.py
msgid "Move all completed jobs to archive"
msgstr ""
msgstr "Alle abgeschlossenen Aufträge ins Archiv verschieben"
#: sabnzbd/skintext.py
msgid "Delete all completed jobs"
msgstr ""
msgstr "Alle abgeschlossenen Aufträge löschen"
#: sabnzbd/skintext.py
msgid "Jobs"
@@ -3281,7 +3295,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Purge Logs"
msgstr ""
msgstr "Protokolle bereinigen"
#: sabnzbd/skintext.py
msgid ".nzb Backup Folder"
@@ -3406,7 +3420,7 @@ msgstr "Aufgabe abgebrochen (verschoben in die Historie)"
#: sabnzbd/skintext.py
msgid "Abort post-processing"
msgstr ""
msgstr "Nachbearbeitung abbrechen"
#: sabnzbd/skintext.py
msgid "Action when unwanted extension detected"
@@ -3479,7 +3493,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
msgstr "Skript zur Beendigung der Warteschlange"
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
@@ -4280,11 +4294,13 @@ msgstr "Geräte, welche die Benachrichtigungen empfangen sollen"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Enable Apprise notifications"
msgstr ""
msgstr "Aktivieren Sie Info-Benachrichtigungen"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgstr ""
"Senden Sie Benachrichtigungen mit Anfragen an fast jeden "
"Benachrichtigungsdienst"
#. Apprise settings
#: sabnzbd/skintext.py
@@ -4295,11 +4311,15 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgstr ""
"Verwenden Sie ein Komma und/oder ein Leerzeichen, um mehr als eine URL zu "
"kennzeichnen."
#: sabnzbd/skintext.py
msgid ""
"Override the default URLs for specific notification types below, if desired."
msgstr ""
"Falls gewünscht, können Sie die Standard-URLs für bestimmte "
"Benachrichtigungstypen unten überschreiben."
#. Header for Notification Script notification section
#: sabnzbd/skintext.py
@@ -4798,6 +4818,9 @@ msgid ""
"When you Retry a job, 'Duplicate Detection' and 'Abort jobs that cannot be "
"completed' are disabled."
msgstr ""
"Wenn Sie einen Auftrag wiederholen, sind die Funktionen „Erkennung von "
"Duplikaten“ und „Abbruch von Aufträgen, die nicht abgeschlossen werden "
"können“ deaktiviert."
#: sabnzbd/skintext.py
msgid "View Script Log"
@@ -5032,7 +5055,7 @@ msgstr ""
#. Error message
#: sabnzbd/sorting.py
msgid "Failed to rename %s to %s"
msgstr "Fehler beim umbennenen von %s nach %s"
msgstr "Fehler beim Umbenennen von %s nach %s"
#. Error message
#: sabnzbd/sorting.py

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
@@ -455,6 +455,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr "Descomprimir directamente"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
@@ -433,6 +433,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2024\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
@@ -469,6 +469,10 @@ msgstr "La désobfuscation a corrigé l'extension de %d fichier(s)"
msgid "Deobfuscate renamed %d file(s)"
msgstr "La désobfuscation a renommé %d fichier(s)"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr "Désobfusquer le(s) fichier(s) de sous-titres renommé(s) %d"
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr "Décompression Directe"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: ION, 2024\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
@@ -437,6 +437,10 @@ msgstr "אי־האפלה תיקנה את הסיומת של %d קבצים"
msgid "Deobfuscate renamed %d file(s)"
msgstr "אי־האפלה שינתה שם של %d קבצים"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr "פריקה ישירה"

View File

@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
"MIME-Version: 1.0\n"
@@ -420,6 +420,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
@@ -431,6 +431,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2024\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
@@ -463,6 +463,10 @@ msgstr "Extensie van %d bestand(en) gecorrigeerd"
msgid "Deobfuscate renamed %d file(s)"
msgstr "Bestandsnamen van %d bestand(en) aangepast."
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr "Direct Uitpakken"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
@@ -430,6 +430,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
@@ -444,6 +444,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
@@ -447,6 +447,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr "Dezarhivare directă"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
@@ -430,6 +430,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
@@ -427,6 +427,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
@@ -428,6 +428,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Kangwei Li <lkw20010211@gmail.com>, 2023\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
@@ -425,6 +425,10 @@ msgstr ""
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha2\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Pavel C <quoing_transifex@mess.cz>, 2022\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"

View File

@@ -9,7 +9,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Lorenz B, 2024\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Ester Molla Aragones <moarages@gmail.com>, 2020\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2024\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: ION, 2024\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"

View File

@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
"MIME-Version: 1.0\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2024\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Petter Ramme, 2024\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
"Project-Id-Version: SABnzbd-4.4.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"

View File

@@ -1,50 +1,51 @@
# Main requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
apprise==1.8.1
apprise==1.9.0
sabctools==8.2.5
CT3==3.3.3.post1
cffi==1.17.0
cffi==1.17.1
pycparser==2.22
feedparser==6.0.11
configobj==5.0.8
configobj==5.0.9
cheroot==10.0.1
six==1.16.0
cherrypy==18.10.0
jaraco.functools==4.0.2
jaraco.functools==4.1.0
jaraco.collections==5.0.0
jaraco.text==3.8.1 # Newer version introduces irrelevant extra dependencies
jaraco.classes==3.4.0
jaraco.context==4.3.0
more-itertools==10.4.0
more-itertools==10.5.0
zc.lockfile==3.0.post1
python-dateutil==2.9.0.post0
tempora==5.7.0
pytz==2024.1
pytz==2024.2
sgmllib3k==1.0.0
portend==3.2.0
chardet==5.2.0
pyunormalize==16.0.0
PySocks==1.7.1
puremagic==1.27
puremagic==1.28
guessit==3.8.0
babelfish==0.6.1
rebulk==3.2.0
# Recent cryptography versions require Rust. If you run into issues compiling this
# SABnzbd will also work with older pre-Rust versions such as cryptography==3.3.2
cryptography==43.0.0
cryptography==43.0.1
# We recommend using "orjson" as it is 2x as fast as "ujson". However, it requires
# Rust so SABnzbd works just as well with "ujson" or the Python built in "json" module
ujson==5.10.0
# Windows system integration
pywin32==306; sys_platform == 'win32'
windows-toasts==1.2.0; sys_platform == 'win32' and python_version > '3.8'
winrt-runtime==2.1.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.Data.Xml.Dom==2.1.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.Foundation==2.1.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.Foundation.Collections==2.1.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.UI.Notifications==2.1.0; sys_platform == 'win32' and python_version > '3.8'
pywin32==308; sys_platform == 'win32'
windows-toasts==1.3.0; sys_platform == 'win32' and python_version > '3.8'
winrt-runtime==2.2.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.Data.Xml.Dom==2.2.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.Foundation==2.2.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.Foundation.Collections==2.2.0; sys_platform == 'win32' and python_version > '3.8'
winrt-Windows.UI.Notifications==2.2.0; sys_platform == 'win32' and python_version > '3.8'
# macOS system calls
pyobjc-core==10.3.1; sys_platform == 'darwin'
@@ -57,14 +58,14 @@ notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
requests==2.32.3
requests-oauthlib==2.0.0
PyYAML==6.0.2
markdown==3.6
markdown==3.7
paho-mqtt==1.6.1 # Pinned, newer versions don't work with AppRise yet
# Requests Requirements
charset_normalizer==3.3.2
idna==3.7
urllib3==2.2.2
certifi==2024.7.4
charset_normalizer==3.4.0
idna==3.10
urllib3==2.2.3
certifi==2024.8.30
oauthlib==3.2.2
PyJWT==2.9.0
blinker==1.8.2

View File

@@ -249,6 +249,7 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
cfg.web_port.callback(cfg.guard_restart)
cfg.web_dir.callback(cfg.guard_restart)
cfg.web_color.callback(cfg.guard_restart)
cfg.url_base.callback(trigger_restart)
cfg.username.callback(cfg.guard_restart)
cfg.password.callback(cfg.guard_restart)
cfg.log_dir.callback(cfg.guard_restart)
@@ -274,30 +275,8 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
# Set end-of-queue action
sabnzbd.misc.change_queue_complete_action(cfg.queue_complete(), new=False)
# Convert auto-sort
if cfg.auto_sort() == "0":
cfg.auto_sort.set("")
elif cfg.auto_sort() == "1":
cfg.auto_sort.set("avg_age asc")
# Convert old series/date/movie sorters
if not cfg.sorters_converted():
misc.convert_sorter_settings()
cfg.sorters_converted.set(True)
# Convert duplicate settings
if cfg.no_series_dupes():
cfg.no_smart_dupes.set(cfg.no_series_dupes())
cfg.no_series_dupes.set(0)
# Convert history retention setting
if cfg.history_retention():
misc.convert_history_retention()
cfg.history_retention.set("")
# Add hostname to the whitelist
if not cfg.host_whitelist():
cfg.host_whitelist.set(socket.gethostname())
# Do any config conversions
cfg.config_conversions()
# Do repair if requested
if misc.check_repair_request():
@@ -319,10 +298,6 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
# Run startup tasks
sabnzbd.NzbQueue.read_queue(repair)
sabnzbd.Scheduler.analyse(pause_downloader)
# Set cache limit for new users
if not cfg.cache_limit():
cfg.cache_limit.set(misc.get_cache_limit())
sabnzbd.ArticleCache.new_limit(cfg.cache_limit.get_int())
logging.info("All processes started")

View File

@@ -115,9 +115,11 @@ def validate_single_tag(value: List[str]) -> Tuple[None, List[str]]:
return None, value
def validate_strip_right_slash(value: str) -> Tuple[None, str]:
"""Strips the right slash"""
if value:
def validate_url_base(value: str) -> Tuple[None, str]:
"""Strips the right slash and adds starting slash, if not present"""
if value and isinstance(value, str):
if not value.startswith("/"):
value = "/" + value
return None, value.rstrip("/")
return None, value
@@ -277,6 +279,9 @@ def validate_default_if_empty(root: str, value: str, default: str) -> Tuple[None
# Special settings
##############################################################################
# Increase everytime we do a configuration conversion
config_conversion_version = OptionNumber("misc", "config_conversion_version", default_val=0)
# This should be here so it's initialized first when the config is read
helpful_warnings = OptionBool("misc", "helpful_warnings", True)
@@ -486,7 +491,7 @@ wait_ext_drive = OptionNumber("misc", "wait_ext_drive", 5, minval=1, maxval=60)
max_foldername_length = OptionNumber("misc", "max_foldername_length", DEF_FOLDER_MAX, minval=20, maxval=65000)
marker_file = OptionStr("misc", "nomedia_marker")
ipv6_servers = OptionBool("misc", "ipv6_servers", True)
url_base = OptionStr("misc", "url_base", "/sabnzbd", validation=validate_strip_right_slash)
url_base = OptionStr("misc", "url_base", "", validation=validate_url_base)
host_whitelist = OptionList("misc", "host_whitelist", validation=all_lowercase)
local_ranges = OptionList("misc", "local_ranges", protect=True)
max_url_retries = OptionNumber("misc", "max_url_retries", 10, minval=1)
@@ -691,7 +696,9 @@ def set_root_folders2():
##############################################################################
def new_limit():
"""Callback for article cache changes"""
sabnzbd.ArticleCache.new_limit(cache_limit.get_int())
if sabnzbd.__INITIALIZED__:
# Only update after full startup
sabnzbd.ArticleCache.new_limit(cache_limit.get_int())
def guard_restart():
@@ -732,3 +739,52 @@ def guard_language():
def guard_https_ver():
"""Callback for change of https verification"""
sabnzbd.misc.set_https_verification(enable_https_verification())
##############################################################################
# Conversions
##############################################################################
def config_conversions():
"""Update sections of the config, only once"""
# Basic old conversions
if config_conversion_version() < 1:
logging.info("Config conversion set 1")
# Convert auto-sort
if auto_sort() == "0":
auto_sort.set("")
elif auto_sort() == "1":
auto_sort.set("avg_age asc")
# Convert old series/date/movie sorters
if not sorters_converted():
sabnzbd.misc.convert_sorter_settings()
sorters_converted.set(True)
# Convert duplicate settings
if no_series_dupes():
no_smart_dupes.set(no_series_dupes())
no_series_dupes.set(0)
# Convert history retention setting
if history_retention():
sabnzbd.misc.convert_history_retention()
history_retention.set("")
# Add hostname to the whitelist
if not host_whitelist():
host_whitelist.set(socket.gethostname())
# Set cache limit for new users
if not cache_limit():
cache_limit.set(sabnzbd.misc.get_cache_limit())
# Done
config_conversion_version.set(1)
# url_base conversion
if config_conversion_version() < 2:
logging.info("Config conversion set 2")
# We did not end up applying this conversion, so we skip this conversion_version
pass

View File

@@ -120,7 +120,8 @@ class Option:
"""Set new value, no validation"""
global CFG_MODIFIED
if value is not None:
if isinstance(value, list) or isinstance(value, dict) or value != self.__value:
# Use get() to make sure we use default if nothing was set yet
if isinstance(value, list) or isinstance(value, dict) or value != self.get():
self.__value = value
CFG_MODIFIED = True
if self.__callback:

View File

@@ -35,7 +35,7 @@ from sabnzbd.constants import DB_HISTORY_NAME, STAGES, Status, PP_LOOKUP
from sabnzbd.bpsmeter import this_week, this_month
from sabnzbd.decorators import synchronized
from sabnzbd.encoding import ubtou, utob
from sabnzbd.misc import caller_name, opts_to_pp, to_units, bool_conv
from sabnzbd.misc import caller_name, opts_to_pp, to_units, bool_conv, match_str
from sabnzbd.filesystem import remove_file, clip_path
DB_LOCK = threading.Lock()
@@ -129,7 +129,7 @@ class HistoryDB:
logging.error(T("Cannot write to History database, check access rights!"))
# Report back success, because there's no recovery possible
return True
elif "not a database" in error or "malformed" in error or "duplicate column name" in error:
elif match_str(error, ("not a database", "malformed", "no such table", "duplicate column name")):
logging.error(T("Damaged History database, created empty replacement"))
logging.info("Traceback: ", exc_info=True)
self.close()
@@ -141,7 +141,7 @@ class HistoryDB:
self.connect()
# Return False in case of "duplicate column" error
# because the column addition in connect() must be terminated
return "duplicate column name" not in error
return True
else:
logging.error(T("SQL Command Failed, see log"))
logging.info("SQL: %s", command)
@@ -604,6 +604,5 @@ def unpack_history_info(item: sqlite3.Row) -> Dict[str, Any]:
def scheduled_history_purge():
logging.info("Scheduled history purge")
with HistoryDB() as history_db:
history_db.auto_history_purge()

View File

@@ -92,7 +92,7 @@ def decode(article: Article, data_view: memoryview):
sabnzbd.Downloader.pause()
# This article should be fetched again
sabnzbd.NzbQueue.reset_try_lists(article)
article.allow_new_fetcher()
return
except BadData as error:

151
sabnzbd/deobfuscate_filenames.py Executable file → Normal file
View File

@@ -27,14 +27,13 @@ files to the job-name in the queue if the filename looks obfuscated
Based on work by P1nGu1n
"""
import hashlib
import logging
import os
import re
import sabnzbd
from sabnzbd.filesystem import get_unique_filename, renamer, get_ext, get_basename
from sabnzbd.filesystem import get_unique_filename, renamer, get_ext, get_basename, listdir_normalized
from sabnzbd.par2file import is_parfile, parse_par2_file
import sabnzbd.utils.file_extension as file_extension
from sabnzbd.misc import match_str
@@ -60,7 +59,7 @@ def decode_par2(parfile: str) -> List[str]:
# Parse all files in the folder
dirname = os.path.dirname(parfile)
new_files = [] # list of new files generated
for fn in os.listdir(dirname):
for fn in listdir_normalized(dirname):
filepath = os.path.join(dirname, fn)
# Only check files
if os.path.isfile(filepath):
@@ -169,21 +168,29 @@ def is_probably_obfuscated(myinputfilename: str) -> bool:
return True # default is obfuscated
def first_file_is_much_bigger(filelist):
# returns True if first file is much bigger than second file
# Note: input parameter filelist must ordered on size!
def get_biggest_file(filelist: List[str]) -> str:
"""Returns biggest file if that file is much bigger than the other files
If only one file exists, return that. If no file, return None
Note: the files in filelist must exist, because their sizes on disk are checked"""
# sort from big to small
filelist = sorted(filelist, key=os.path.getsize)[::-1] # reversed, so big to small. Format [start:stop:step]
try:
factor = os.path.getsize(filelist[0]) / os.path.getsize(filelist[1])
if factor > 3:
return True
return filelist[0]
else:
return False
except:
# no second file at all
return True
if len(filelist) == 1:
# the only file, so biggest
return filelist[0]
else:
# no existing file(s)
return None
def deobfuscate(nzo, filelist: List[str], usefulname: str):
def deobfuscate(nzo, filelist: List[str], usefulname: str) -> List[str]:
"""
For files in filelist:
1. if a file has no meaningful extension, add it (for example ".txt" or ".png")
@@ -225,69 +232,60 @@ def deobfuscate(nzo, filelist: List[str], usefulname: str):
nzo: sabnzbd.nzbstuff.NzbObject
# to be sure, only keep really existing files and remove any duplicates:
filelist = set(f for f in filelist if os.path.isfile(f))
filtered_filelist = list(set(f for f in filelist if os.path.isfile(f)))
# Do not deobfuscate/rename anything if there is a typical DVD or Bluray directory:
ignored_movie_folders_with_dir_sep = tuple(os.path.sep + f + os.path.sep for f in IGNORED_MOVIE_FOLDERS)
match_ignored_movie_folders = [f for f in filelist if match_str(f, ignored_movie_folders_with_dir_sep)]
match_ignored_movie_folders = [f for f in filtered_filelist if match_str(f, ignored_movie_folders_with_dir_sep)]
if match_ignored_movie_folders:
logging.info(
"Skipping deobfuscation because of DVD/Bluray directory name(s), like: %s",
str(match_ignored_movie_folders)[:200],
)
nzo.set_unpack_info("Deobfuscate", T("Deobfuscate skipped due to DVD/Bluray directories"))
return
return filtered_filelist
# If needed, add a useful extension (by looking at file contents)
# Example: if 'kjladsflkjadf.adsflkjads' is probably a PNG, rename to 'kjladsflkjadf.adsflkjads.png'
newlist = []
new_filelist = []
nr_ext_renamed = 0
for file in filelist:
for file in filtered_filelist:
if file_extension.has_popular_extension(file):
# common extension, like .doc or .iso, so assume OK and change nothing
logging.debug("Extension of %s looks common", file)
newlist.append(file)
new_filelist.append(file)
else:
# uncommon (so: obfuscated) extension
new_extension_to_add = file_extension.what_is_most_likely_extension(file)
if new_extension_to_add:
if new_extension_to_add := file_extension.what_is_most_likely_extension(file):
new_name = get_unique_filename("%s%s" % (file, new_extension_to_add))
logging.info("Deobfuscate renaming (adding extension) %s to %s", file, new_name)
renamer(file, new_name)
newlist.append(new_name)
# Use output of renamer, just in case it's somehow modified by sanitization
new_filelist.append(renamer(file, new_name))
nr_ext_renamed += 1
else:
# no new extension found
newlist.append(file)
new_filelist.append(file)
if nr_ext_renamed:
nzo.set_unpack_info("Deobfuscate", T("Deobfuscate corrected the extension of %d file(s)") % nr_ext_renamed)
filelist = newlist
filtered_filelist = new_filelist
logging.debug("Trying to see if there are qualifying files to be deobfuscated")
nr_files_renamed = 0
# We pick the biggest file ... probably the most important file
# so sort filelist on size:
filelist = sorted(filelist, key=os.path.getsize, reverse=True)
if filelist:
biggest_file = filelist[0]
else:
biggest_file = None
if not biggest_file or not os.path.isfile(biggest_file):
logging.debug("Trying to see if there are qualifying files to be deobfuscated")
if not (biggest_file := get_biggest_file(filtered_filelist)) or not os.path.isfile(biggest_file):
# no file found, which is weird
logging.info("No file given, or not found (%s)", biggest_file)
return
logging.info("No biggest file found, or not found (%s)", biggest_file)
return filtered_filelist
logging.debug("Deobfuscate inspecting biggest file%s", biggest_file)
if not first_file_is_much_bigger(filelist):
logging.debug("%s excluded from deobfuscation because it is not much bigger than other file(s)", biggest_file)
return
if get_ext(biggest_file) in EXCLUDED_FILE_EXTS:
logging.debug("%s excluded from deobfuscation because of excluded extension", biggest_file)
return
return filtered_filelist
if not is_probably_obfuscated(biggest_file):
logging.debug("%s excluded from deobfuscation because filename does not look obfuscated", biggest_file)
return
return filtered_filelist
# if we get here, the biggest_file is relatively big, has no excluded extension, and is obfuscated
# Rename the biggest_file and make sure the new filename is unique
@@ -295,20 +293,89 @@ def deobfuscate(nzo, filelist: List[str], usefulname: str):
# construct new_name: <path><usefulname><extension>
new_name = get_unique_filename("%s%s" % (os.path.join(path, usefulname), get_ext(biggest_file)))
logging.info("Deobfuscate renaming %s to %s", biggest_file, new_name)
renamer(biggest_file, new_name)
filtered_filelist.remove(biggest_file)
filtered_filelist.append(renamer(biggest_file, new_name))
nr_files_renamed += 1
# Now find other files with the same basename in filelist, and rename them in the same way:
basedirfile = get_basename(biggest_file) # something like "/home/this/myiso"
for otherfile in filelist:
for otherfile in filtered_filelist[:]:
if otherfile.startswith(basedirfile) and os.path.isfile(otherfile):
# yes, same basedirfile, only different ending
remaining_ending = otherfile.replace(basedirfile, "") # might be long ext, like ".dut.srt" or "-sample.iso"
new_name = get_unique_filename("%s%s" % (os.path.join(path, usefulname), remaining_ending))
logging.info("Deobfuscate renaming %s to %s", otherfile, new_name)
# Rename and make sure the new filename is unique
renamer(otherfile, new_name)
filtered_filelist.remove(otherfile)
filtered_filelist.append(renamer(otherfile, new_name))
nr_files_renamed += 1
if nr_files_renamed:
nzo.set_unpack_info("Deobfuscate", T("Deobfuscate renamed %d file(s)") % nr_files_renamed)
return filtered_filelist
def without_extension(fullpathfilename: str) -> str:
"""Returns full file path, without extension
So '/some/dir/somefile.bin' results in '/some/dir/somefile'"""
return os.path.splitext(fullpathfilename)[0]
def deobfuscate_subtitles(nzo, filelist: List[str]):
"""
input:
nzo, so we can update result via set_unpack_info()
filelist must be a List of existing filenames
Find .srt subtitle files, and rename to match the biggest file (if there is a clearly biggest file)
Some_Big_File_2024.avi # biggest file
Some_Big_File_2024.srt # no renaming wanted
Some_Big_File_2024.ger.srt # no renaming wanted
14_English.srt # to be renamed
dut.srt # to be renamed
Something.else.txt # no renaming wanted, because no .srt
will result in
Some_Big_File_2024.avi
Some_Big_File_2024.srt
Some_Big_File_2024.ger.srt
Some_Big_File_2024.14_English.srt # renamed by prepending base name
Some_Big_File_2024.dut.srt # renamed by prepending base name
Something.else.txt
"""
# Can't be imported directly due to circular import
nzo: sabnzbd.nzbstuff.NzbObject
# find .srt files
if not (srt_files := [f for f in filelist if f.endswith(".srt")]):
logging.debug("No .srt files found, so nothing to do")
return None
# check there is a clearly biggest file
if not (biggest_file := get_biggest_file(filelist)):
logging.debug("No clearly biggest file found, so no subtitle renaming feasible")
return None
biggest_file_without_ext = without_extension(biggest_file) # get full path base name of biggest file
logging.debug(f"Using as base filename: {biggest_file_without_ext}")
# handle srt files one by one
nr_files_renamed = 0
for srt_file in srt_files:
if without_extension(srt_file).startswith(biggest_file_without_ext):
# already the same start as the biggest file, so skip
continue
# not the same start, so rename the srt file
nr_files_renamed += 1
filename_only = os.path.basename(srt_file) # like "14_English.srt", so without path
# now put that name after the base name of the biggestfile:
new_full_name = f"{biggest_file_without_ext}.{filename_only}" # put (renamed) srt behind that
unique_filename = get_unique_filename(new_full_name) # make sure it's really unique
renamer(srt_file, unique_filename) # ... and rename actual file on disk
if nr_files_renamed > 0:
# and put it into history to be shown in GUI
nzo.set_unpack_info("Deobfuscate", T("Deobfuscate renamed %d subtitle file(s)") % nr_files_renamed)

View File

@@ -165,11 +165,12 @@ class Server:
self.reset_article_queue()
def stop(self):
"""Remove all connections from server"""
"""Remove all connections and cached articles from server"""
for nw in self.idle_threads:
sabnzbd.Downloader.remove_socket(nw)
nw.hard_reset()
self.idle_threads = set()
self.reset_article_queue()
@synchronized(DOWNLOADER_LOCK)
def get_article(self):
@@ -195,10 +196,13 @@ class Server:
self.next_article_search = time.time() + _SERVER_CHECK_DELAY
return None
@synchronized(DOWNLOADER_LOCK)
def reset_article_queue(self):
logging.debug("Resetting article queue for %s", self)
"""Reset articles queued for the Server. Locked to prevent
articles getting stuck in the Server when enabled/disabled"""
logging.debug("Resetting article queue for %s (%s)", self, self.article_queue)
for article in self.article_queue:
sabnzbd.NzbQueue.reset_try_lists(article)
article.allow_new_fetcher()
self.article_queue = []
def request_addrinfo(self):
@@ -211,7 +215,7 @@ class Server:
def request_addrinfo_blocking(self):
"""Blocking attempt to run getaddrinfo() and Happy Eyeballs for specified server"""
logging.debug("Retrieving server address information for %s", self.host)
logging.debug("Retrieving server address information for %s", self)
# Disable IPV6 if desired
family = socket.AF_UNSPEC
@@ -229,7 +233,7 @@ class Server:
sabnzbd.Downloader.wakeup()
def __repr__(self):
return "<Server: %s:%s>" % (self.host, self.port)
return "<Server: id=%s, host=%s:%s>" % (self.id, self.host, self.port)
class Downloader(Thread):
@@ -329,7 +333,6 @@ class Downloader(Thread):
create = False
server.newid = newserver
server.restart = True
server.reset_article_queue()
self.server_restarts += 1
break
@@ -965,9 +968,9 @@ class Downloader(Thread):
self.decode(nw.article)
nw.article.tries = 0
else:
# Allow all servers again on this server
# Allow all servers again for this article
# Do not use the article_queue, as the server could already have been disabled when we get here!
sabnzbd.NzbQueue.reset_try_lists(nw.article)
nw.article.allow_new_fetcher()
# Reset connection object
nw.hard_reset(wait)

View File

@@ -20,6 +20,7 @@ sabnzbd.encoding - Unicode/byte translation functions
"""
import locale
import pyunormalize
import chardet
from xml.sax.saxutils import escape
from typing import AnyStr
@@ -27,6 +28,11 @@ from typing import AnyStr
CODEPAGE = locale.getpreferredencoding()
def normalize_utf8(inputstring: str) -> str:
"""Make sure we return an utf8 normalized version"""
return pyunormalize.NFC(inputstring)
def utob(str_in: AnyStr) -> bytes:
"""Shorthand for converting UTF-8 string to bytes"""
if isinstance(str_in, bytes):
@@ -37,22 +43,19 @@ def utob(str_in: AnyStr) -> bytes:
def ubtou(str_in: AnyStr) -> str:
"""Shorthand for converting unicode bytes to UTF-8 string"""
if isinstance(str_in, str):
return str_in
return str_in.decode("utf-8")
return normalize_utf8(str_in)
return normalize_utf8(str_in.decode("utf-8"))
def platform_btou(str_in: AnyStr) -> str:
"""Return Unicode string, if not already Unicode, decode with locale encoding.
NOTE: Used for POpen because universal_newlines/text parameter doesn't
always work! We cannot use encoding-parameter because it's Python 3.7+
"""
"""Return Unicode string, if not already Unicode, decode with locale encoding"""
if isinstance(str_in, bytes):
try:
return ubtou(str_in)
except UnicodeDecodeError:
return str_in.decode(CODEPAGE, errors="replace").replace("?", "!")
return normalize_utf8(str_in.decode(CODEPAGE, errors="replace").replace("?", "!"))
else:
return str_in
return normalize_utf8(str_in)
def correct_unknown_encoding(str_or_bytes_in: AnyStr) -> str:
@@ -71,10 +74,10 @@ def correct_unknown_encoding(str_or_bytes_in: AnyStr) -> str:
except UnicodeDecodeError:
try:
# Try using 8-bit ASCII, if came from Windows
return str_or_bytes_in.decode("ISO-8859-1")
return normalize_utf8(str_or_bytes_in.decode("ISO-8859-1"))
except ValueError:
# Last resort we use the slow chardet package
return str_or_bytes_in.decode(chardet.detect(str_or_bytes_in)["encoding"])
return normalize_utf8(str_or_bytes_in.decode(chardet.detect(str_or_bytes_in)["encoding"]))
def correct_cherrypy_encoding(inputstring: str) -> str:

View File

@@ -46,7 +46,7 @@ except ImportError:
import sabnzbd
from sabnzbd.decorators import synchronized, cache_maintainer
from sabnzbd.constants import FUTURE_Q_FOLDER, JOB_ADMIN, GIGI, DEF_FILE_MAX, IGNORED_FILES_AND_FOLDERS, DEF_LOG_FILE
from sabnzbd.encoding import correct_unknown_encoding, utob, ubtou
from sabnzbd.encoding import correct_unknown_encoding, utob, ubtou, normalize_utf8
from sabnzbd.utils import rarfile
@@ -561,7 +561,7 @@ def globber(path: str, pattern: str = "*") -> List[str]:
"""Return matching base file/folder names in folder `path`"""
# Cannot use glob.glob() because it doesn't support Windows long name notation
if os.path.exists(path):
return [f for f in os.listdir(path) if safe_fnmatch(f, pattern)]
return [f for f in listdir_normalized(path) if safe_fnmatch(f, pattern)]
return []
@@ -569,7 +569,8 @@ def globber_full(path: str, pattern: str = "*") -> List[str]:
"""Return matching full file/folder names in folder `path`"""
# Cannot use glob.glob() because it doesn't support Windows long name notation
if os.path.exists(path):
return [os.path.join(path, f) for f in os.listdir(path) if safe_fnmatch(f, pattern)]
path = normalize_utf8(path)
return [os.path.join(path, f) for f in listdir_normalized(path) if safe_fnmatch(f, pattern)]
return []
@@ -581,7 +582,7 @@ def fix_unix_encoding(folder: str):
if not sabnzbd.WIN32 and not sabnzbd.MACOS:
for root, dirs, files in os.walk(folder):
for name in files:
new_name = correct_unknown_encoding(name)
new_name = normalize_utf8(correct_unknown_encoding(name))
if name != new_name:
try:
renamer(os.path.join(root, name), os.path.join(root, new_name))
@@ -804,6 +805,12 @@ def get_unique_filename(path: str) -> str:
return path
def listdir_normalized(input_dir: str) -> List[str]:
"""On macOS, the OS returns un-normalized results.
Always use the same normalization on all platforms"""
return [normalize_utf8(path) for path in os.listdir(input_dir)]
@synchronized(DIR_LOCK)
def listdir_full(input_dir: str, recursive: bool = True) -> List[str]:
"""List all files in dirs and sub-dirs"""
@@ -812,7 +819,7 @@ def listdir_full(input_dir: str, recursive: bool = True) -> List[str]:
for file in files:
# Ignore special folders and resources files created by macOS
if not sabnzbd.misc.match_str(root, IGNORED_FILES_AND_FOLDERS) and not file.startswith("._"):
filelist.append(os.path.join(root, file))
filelist.append(normalize_utf8(os.path.join(root, file)))
if not recursive:
break
return filelist
@@ -1386,7 +1393,7 @@ def pathbrowser(path: str, show_hidden: bool = False, show_files: bool = False)
# List all files and folders
file_list = []
for filename in os.listdir(path):
for filename in listdir_normalized(path):
fpath = os.path.join(path, filename)
isdir = os.path.isdir(fpath)

View File

@@ -395,9 +395,8 @@ def Raiser(root: str = "", **kwargs):
if kwargs:
root = "%s?%s" % (root, urllib.parse.urlencode(kwargs))
# Optionally add the leading /sabnzbd/ (or what the user set)
if not root.startswith(cfg.url_base()):
root = cherrypy.request.script_name + root
# Add the leading /sabnzbd/ (or what the user set)
root = cfg.url_base() + root
# Log the redirect
if cfg.api_logging():
@@ -642,7 +641,7 @@ class LoginPage:
# Check if there's even a username/password set
if check_login():
raise Raiser(cherrypy.request.script_name + "/")
raise Raiser("/")
# Check login info
if kwargs.get("username") == cfg.username() and kwargs.get("password") == cfg.password():
@@ -651,7 +650,7 @@ class LoginPage:
# Log the success
logging.info("Successful login from %s", cherrypy.request.remote_label)
# Redirect
raise Raiser(cherrypy.request.script_name + "/")
raise Raiser("/")
elif kwargs.get("username") or kwargs.get("password"):
info["error"] = T("Authentication failed, check username/password.")
# Warn about the potential security problem

View File

@@ -63,6 +63,7 @@ from sabnzbd.filesystem import (
SEVENMULTI_RE,
is_size,
get_basename,
listdir_normalized,
)
from sabnzbd.nzbstuff import NzbObject
import sabnzbd.cfg as cfg
@@ -1020,7 +1021,7 @@ def par2_repair(nzo: NzbObject, setname: str) -> Tuple[bool, bool]:
return False, True
parfile = os.path.join(nzo.download_path, parfile_nzf.filename)
old_dir_content = os.listdir(nzo.download_path)
old_dir_content = listdir_normalized(nzo.download_path)
used_joinables = ()
joinables = ()
used_for_repair = ()
@@ -1080,7 +1081,7 @@ def par2_repair(nzo: NzbObject, setname: str) -> Tuple[bool, bool]:
try:
if cfg.enable_par_cleanup():
deletables = []
new_dir_content = os.listdir(nzo.download_path)
new_dir_content = listdir_normalized(nzo.download_path)
# If Multipar or par2cmdline repairs a broken part of a joinable, it doesn't list it as such.
# So we need to manually add all joinables of the set to the list of used joinables.

View File

@@ -669,16 +669,6 @@ class NzbQueue:
except:
return -1
@staticmethod
def reset_try_lists(article: Article, remove_fetcher_from_trylist: bool = True):
"""Let article get new fetcher and reset trylists"""
if remove_fetcher_from_trylist:
article.remove_from_try_list(article.fetcher)
article.fetcher = None
article.tries = 0
article.nzf.reset_try_list()
article.nzf.nzo.reset_try_list()
def has_forced_jobs(self) -> bool:
"""Check if the queue contains any Forced
Priority jobs to download while paused
@@ -885,7 +875,7 @@ class NzbQueue:
logging.info("Found idle job %s", nzo.final_name)
empty.append(nzo)
# Stall prevention by checking if all servers are in the trylist
# Stall prevention by checking if all servers are in the try list
# This is a CPU-cheaper alternative to prevent stalling
if nzo.all_servers_in_try_list(active_servers):
# Maybe the NZF's need a reset too?
@@ -904,7 +894,7 @@ class NzbQueue:
logging.info("Resetting bad trylist for file %s in job %s", nzf.filename, nzo.final_name)
nzf.reset_try_list()
# Reset main trylist, minimal performance impact
# Reset main try list, minimal performance impact
logging.info("Resetting bad trylist for job %s", nzo.final_name)
nzo.reset_try_list()

View File

@@ -111,7 +111,7 @@ RE_RAR = re.compile(r"(\.rar|\.r\d\d|\.s\d\d|\.t\d\d|\.u\d\d|\.v\d\d)$", re.I)
# Trylist
##############################################################################
TRYLIST_LOCK = threading.Lock()
TRYLIST_LOCK = threading.RLock()
class TryList:
@@ -203,12 +203,25 @@ class Article(TryList):
self.crc32: Optional[int] = None
self.nzf: NzbFile = nzf
@synchronized(TRYLIST_LOCK)
def reset_try_list(self):
"""In addition to resetting the try list, also reset fetcher so all servers are tried again"""
"""In addition to resetting the try list, also reset fetcher so all servers
are tried again. Locked so fetcher setting changes are also protected."""
self.fetcher = None
self.fetcher_priority = 0
super().reset_try_list()
@synchronized(TRYLIST_LOCK)
def allow_new_fetcher(self, remove_fetcher_from_try_list: bool = True):
"""Let article get new fetcher and reset try lists of file and job.
Locked so all resets are performed at once"""
if remove_fetcher_from_try_list:
self.remove_from_try_list(self.fetcher)
self.fetcher = None
self.tries = 0
self.nzf.reset_try_list()
self.nzf.nzo.reset_try_list()
def get_article(self, server: Server, servers: List[Server]):
"""Return article when appropriate for specified server"""
if self.fetcher or self.server_in_try_list(server):
@@ -249,7 +262,7 @@ class Article(TryList):
if server.priority >= self.fetcher.priority:
self.tries = 0
# Allow all servers for this nzo and nzf again (but not this fetcher for this article)
sabnzbd.NzbQueue.reset_try_lists(self, remove_fetcher_from_trylist=False)
self.allow_new_fetcher(remove_fetcher_from_try_list=False)
return True
logging.info("Article %s unavailable on all servers, discarding", self.article)
@@ -276,17 +289,6 @@ class Article(TryList):
self.fetcher_priority = 0
self.tries = 0
def __eq__(self, other):
"""Articles with the same usenet address are the same"""
return self.article == other.article
def __hash__(self):
"""Required because we implement eq. Articles with the same
usenet address can appear in different NZF's. So we make every
article object unique, even though it is bad practice.
"""
return id(self)
def __repr__(self):
return "<Article: article=%s, bytes=%s, art_id=%s>" % (self.article, self.bytes, self.art_id)
@@ -431,8 +433,11 @@ class NzbFile(TryList):
self.add_to_try_list(server)
return articles
@synchronized(TRYLIST_LOCK)
def reset_all_try_lists(self):
"""Clear all lists of visited servers"""
"""Reset all try lists. Locked so reset is performed
for all items at the same time without chance of another
thread changing any of the items while we are resetting"""
for art in self.articles:
art.reset_try_list()
self.reset_try_list()
@@ -481,10 +486,12 @@ class NzbFile(TryList):
"""Assume it's the same file if the number bytes and first article
are the same or if there are no articles left, use the filenames.
Some NZB's are just a mess and report different sizes for the same article.
We used to compare (__eq__) articles based on article-ID, however, this failed
because some NZB's had the same article-ID twice within one NZF.
"""
if other and (self.bytes == other.bytes or len(self.decodetable) == len(other.decodetable)):
if self.decodetable and other.decodetable:
return self.decodetable[0] == other.decodetable[0]
return self.decodetable[0].article == other.decodetable[0].article
# Fallback to filename comparison
return self.filename == other.filename
return False
@@ -1001,7 +1008,11 @@ class NzbObject(TryList):
except:
logging.debug("The lastrar swap did not go well")
@synchronized(TRYLIST_LOCK)
def reset_all_try_lists(self):
"""Reset all try lists. Locked so reset is performed
for all items at the same time without chance of another
thread changing any of the items while we are resetting"""
for nzf in self.files:
nzf.reset_all_try_lists()
self.reset_try_list()
@@ -1442,7 +1453,7 @@ class NzbObject(TryList):
@synchronized(NZO_LOCK)
def add_parfile(self, parfile: NzbFile) -> bool:
"""Add parfile to the files to be downloaded
Resets trylist just to be sure
Resets try list just to be sure
Adjust download-size accordingly
Returns False when the file couldn't be added
"""

View File

@@ -73,6 +73,7 @@ from sabnzbd.filesystem import (
get_filename,
directory_is_writable,
check_filesystem_capabilities,
listdir_normalized,
)
from sabnzbd.nzbstuff import NzbObject
from sabnzbd.sorting import Sorter
@@ -486,12 +487,12 @@ def process_job(nzo: NzbObject) -> bool:
if all_ok:
# Move any (left-over) files to destination
nzo.status = Status.MOVING
nzo.set_action_line(T("Moving"), "...")
for root, _, files in os.walk(nzo.download_path):
if not root.endswith(JOB_ADMIN):
for file in files:
path = os.path.join(root, file)
new_path = path.replace(nzo.download_path, tmp_workdir_complete)
nzo.set_action_line(T("Moving"), file)
ok, new_path = move_to_path(path, new_path)
if new_path:
newfiles.append(new_path)
@@ -573,7 +574,9 @@ def process_job(nzo: NzbObject) -> bool:
if cfg.deobfuscate_final_filenames():
# Deobfuscate the filenames
logging.info("Running deobfuscate")
deobfuscate.deobfuscate(nzo, newfiles, nzo.final_name)
newfiles = deobfuscate.deobfuscate(nzo, newfiles, nzo.final_name)
# Deobfuscate the subtitles
deobfuscate.deobfuscate_subtitles(nzo, newfiles)
# Run the user script
if script_path := make_script_path(script):
@@ -959,7 +962,7 @@ def rar_renamer(nzo: NzbObject) -> int:
volnrext = {}
# Scan rar files in workdir, but not subdirs
workdir_files = os.listdir(nzo.download_path)
workdir_files = listdir_normalized(nzo.download_path)
for file_to_check in workdir_files:
file_to_check = os.path.join(nzo.download_path, file_to_check)
@@ -1183,7 +1186,7 @@ def one_file_or_folder(folder: str) -> str:
"""If the dir only contains one file or folder, join that file/folder onto the path"""
if os.path.exists(folder) and os.path.isdir(folder):
try:
cont = os.listdir(folder)
cont = listdir_normalized(folder)
if len(cont) == 1:
folder = os.path.join(folder, cont[0])
folder = one_file_or_folder(folder)

View File

@@ -37,6 +37,7 @@ from sabnzbd.filesystem import (
renamer,
sanitize_foldername,
clip_path,
listdir_normalized,
)
import sabnzbd.config as config
import sabnzbd.cfg as cfg
@@ -616,7 +617,7 @@ def move_to_parent_directory(workdir: str) -> Tuple[str, bool]:
logging.debug("Moving all files from %s to %s", workdir, dest)
# Check for DVD folders and bail out if found
for item in os.listdir(workdir):
for item in listdir_normalized(workdir):
if item.lower() in IGNORED_MOVIE_FOLDERS:
return workdir, True

View File

@@ -6,5 +6,5 @@
# You MUST use double quotes (so " and not ')
# Do not forget to update the appdata file for every major release!
__version__ = "4.3.3"
__baseline__ = "157dfc928d2a4fef13182ea64ab8d0b533d677c4"
__version__ = "4.4.0Alpha2"
__baseline__ = "unknown"

View File

@@ -134,6 +134,7 @@ def run_sabnews_and_selenium(request):
# Headless during CI testing
if "CI" in os.environ:
driver_options.browser_version = "127"
driver_options.add_argument("--headless")
driver_options.add_argument("--no-sandbox")

View File

@@ -7,7 +7,7 @@ strict:
stages:
- name: get_files format (json)
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: get_files
@@ -39,7 +39,7 @@ strict:
stages:
- name: get_files format (xml)
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: get_files

View File

@@ -7,7 +7,7 @@ strict:
stages:
- name: history format empty
request:
url: http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api
url: http://{SAB_HOST}:{SAB_PORT}/api
method: GET
params:
mode: history
@@ -38,7 +38,7 @@ test_name: Check empty history format (xml output)
stages:
- name: history format empty
request:
url: http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api
url: http://{SAB_HOST}:{SAB_PORT}/api
method: GET
params:
mode: history

View File

@@ -7,7 +7,7 @@ strict:
stages:
- name: history format single entry
request:
url: http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api
url: http://{SAB_HOST}:{SAB_PORT}/api
method: GET
params:
mode: history
@@ -70,7 +70,7 @@ test_name: Check general history format (xml output)
stages:
- name: history format single entry
request:
url: http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api
url: http://{SAB_HOST}:{SAB_PORT}/api
method: GET
params:
mode: history
@@ -145,7 +145,7 @@ strict:
stages:
- name: history slot count
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: history

View File

@@ -7,7 +7,7 @@ strict:
stages:
- name: queue format empty
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: queue
@@ -61,7 +61,7 @@ test_name: Check empty queue format (xml output)
stages:
- name: queue format empty
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: queue

View File

@@ -7,7 +7,7 @@ strict:
stages:
- name: queue format single entry
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: queue
@@ -81,7 +81,7 @@ test_name: Check general queue format (xml output)
stages:
- name: queue format single entry
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: queue

View File

@@ -7,7 +7,7 @@ strict:
stages:
- name: server_stats (json output)
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: server_stats
@@ -31,7 +31,7 @@ stages:
- name: server_stats (xml output)
request:
url: "http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api"
url: "http://{SAB_HOST}:{SAB_PORT}/api"
method: GET
params:
mode: server_stats

View File

@@ -7,7 +7,7 @@ strict:
stages:
- name: version (json output)
request:
url: http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api
url: http://{SAB_HOST}:{SAB_PORT}/api
method: GET
params:
mode: version
@@ -22,7 +22,7 @@ stages:
- name: version (xml output)
request:
url: http://{SAB_HOST}:{SAB_PORT}/sabnzbd/api
url: http://{SAB_HOST}:{SAB_PORT}/api
method: GET
params:
mode: version

View File

@@ -18,7 +18,7 @@
"""
Testing SABnzbd deobfuscate module
"""
import os.path
import random
import shutil
import zipfile
@@ -404,3 +404,83 @@ class TestDeobfuscateFinalResult:
assert not os.path.isfile(os.path.join(work_dir, "twentymb.bin")) # should now be gone
shutil.rmtree(work_dir)
def test_get_biggest_file(self):
# Create directory (with a random directory name)
dirname = os.path.join(SAB_CACHE_DIR, "testdir" + str(random.randint(10000, 99999)))
os.mkdir(dirname)
smallfile1 = os.path.join(dirname, "AAAA.bin")
create_small_file(smallfile1)
assert os.path.isfile(smallfile1)
bigfile = os.path.join(dirname, "KKKK.bin")
create_big_file(bigfile)
assert os.path.isfile(bigfile)
smallfile2 = os.path.join(dirname, "LLLL.bin")
create_small_file(smallfile2)
assert os.path.isfile(smallfile2)
# empty list should return None
assert not get_biggest_file([])
# just 1 file as input is always the biggest file
assert get_biggest_file([smallfile1]) == smallfile1 # just 1 file, so that's the biggest
# files with same small size, so no biggest file
assert not get_biggest_file([smallfile1, smallfile2])
# now including the bigger file
assert get_biggest_file([smallfile1, smallfile2, bigfile]) == bigfile
shutil.rmtree(dirname)
def test_deobfuscate_subtitles(self):
# input: a big file, and srt file(s), and non-related files
# result: srt file renamed according to the big file
"""Wrapper to avoid the need for NZO"""
nzo = mock.Mock()
nzo.set_unpack_info = mock.Mock()
# Create directory (with a random directory name)
dirname = os.path.join(SAB_CACHE_DIR, "testdir" + str(random.randint(10000, 99999)))
os.mkdir(dirname)
bigfile = os.path.join(dirname, "bigfile.avi")
create_big_file(bigfile)
assert os.path.isfile(bigfile)
already_correct_srt = os.path.join(dirname, "bigfile.srt")
create_small_file(already_correct_srt)
assert os.path.isfile(already_correct_srt)
small_srt = os.path.join(dirname, "dut.srt")
create_small_file(small_srt)
assert os.path.isfile(small_srt)
expected_small_srt = os.path.join(dirname, "bigfile.dut.srt")
small_txt = os.path.join(dirname, "readme.txt")
create_small_file(small_txt)
assert os.path.isfile(small_txt)
# go
deobfuscate_subtitles(nzo, [bigfile, already_correct_srt, small_srt, small_txt])
assert os.path.isfile(bigfile) # unchanged
assert os.path.isfile(already_correct_srt) # unchanged
assert not os.path.isfile(small_srt) # should be renamed to:
assert os.path.isfile(expected_small_srt)
assert os.path.isfile(small_txt) # unchanged
# and if we go again ... nothing should happen: all files are already correct
deobfuscate_subtitles(nzo, [bigfile, already_correct_srt, expected_small_srt, small_txt])
assert os.path.isfile(bigfile) # unchanged
assert os.path.isfile(already_correct_srt) # unchanged
assert not os.path.isfile(small_srt) # should be renamed to:
assert os.path.isfile(expected_small_srt)
assert os.path.isfile(small_txt) # unchanged
shutil.rmtree(dirname)

View File

@@ -79,7 +79,7 @@ class TestBasicPages(SABnzbdBaseTest):
class TestConfigLogin(SABnzbdBaseTest):
def test_login(self):
# Test if base page works
self.open_page("http://%s:%s/sabnzbd/config/general" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/config/general" % (SAB_HOST, SAB_PORT))
# Set the username and password
username_imp = self.selenium_wrapper(self.driver.find_element, By.CSS_SELECTOR, "input[data-hide='username']")
@@ -102,7 +102,7 @@ class TestConfigLogin(SABnzbdBaseTest):
pass
# Open any page and check if we get redirected
self.open_page("http://%s:%s/sabnzbd/general" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/general" % (SAB_HOST, SAB_PORT))
assert "/login/" in self.driver.current_url
# Fill nonsense and submit
@@ -130,7 +130,7 @@ class TestConfigLogin(SABnzbdBaseTest):
self.driver.find_element(By.TAG_NAME, "button").click()
# Can we now go to the page and empty the settings again?
self.open_page("http://%s:%s/sabnzbd/config/general" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/config/general" % (SAB_HOST, SAB_PORT))
assert "/login/" not in self.driver.current_url
# Set the username and password
@@ -152,7 +152,7 @@ class TestConfigLogin(SABnzbdBaseTest):
pass
# Open any page and check if we get redirected
self.open_page("http://%s:%s/sabnzbd/general" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/general" % (SAB_HOST, SAB_PORT))
assert "/login/" not in self.driver.current_url
@@ -161,7 +161,7 @@ class TestConfigCategories(SABnzbdBaseTest):
def test_page(self):
# Test if base page works
self.open_page("http://%s:%s/sabnzbd/config/categories" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/config/categories" % (SAB_HOST, SAB_PORT))
# Add new category
self.driver.find_elements(By.NAME, "newname")[1].send_keys("testCat")
@@ -189,7 +189,7 @@ class TestConfigRSS(SABnzbdBaseTest):
rss_url = httpserver.url_for("/rss_feed.xml")
# Test if base page works
self.open_page("http://%s:%s/sabnzbd/config/rss" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/config/rss" % (SAB_HOST, SAB_PORT))
# Uncheck enabled-checkbox for new feeds
self.selenium_wrapper(
@@ -256,7 +256,7 @@ class TestConfigServers(SABnzbdBaseTest):
def open_config_servers(self):
# Test if base page works
self.open_page("http://%s:%s/sabnzbd/config/server" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/config/server" % (SAB_HOST, SAB_PORT))
self.scroll_to_top()
# Show advanced options

View File

@@ -287,7 +287,7 @@ class DownloadFlowBasics(SABnzbdBaseTest):
def start_wizard(self):
# Language-selection
self.open_page("http://%s:%s/sabnzbd/wizard/" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/wizard/" % (SAB_HOST, SAB_PORT))
self.selenium_wrapper(self.driver.find_element, By.ID, "en").click()
self.selenium_wrapper(self.driver.find_element, By.CSS_SELECTOR, "button.btn.btn-default").click()
@@ -317,7 +317,7 @@ class DownloadFlowBasics(SABnzbdBaseTest):
self.selenium_wrapper(self.driver.find_element, By.ID, "next-button").click()
self.no_page_crash()
check_result = self.selenium_wrapper(self.driver.find_element, By.CLASS_NAME, "quoteBlock").text
assert "http://%s:%s/sabnzbd" % (SAB_HOST, SAB_PORT) in check_result
assert "http://%s:%s/" % (SAB_HOST, SAB_PORT) in check_result
# Go to SAB!
self.selenium_wrapper(self.driver.find_element, By.CSS_SELECTOR, ".btn.btn-success").click()
@@ -342,7 +342,7 @@ class DownloadFlowBasics(SABnzbdBaseTest):
os.remove(nzb_path)
# See how it's doing
self.open_page("http://%s:%s/sabnzbd/" % (SAB_HOST, SAB_PORT))
self.open_page("http://%s:%s/" % (SAB_HOST, SAB_PORT))
# We wait for 20 seconds to let it complete
for _ in range(20):

View File

Binary file not shown.

View File

@@ -1,177 +1,183 @@
Restore damaged or lost files with PAR recovery files.
MultiPar (set of PAR clients and GUI)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ Introduction ]
MultiPar was made as an alternative to QuickPar.
The GUI is similar to QuickPar by getting agreement from Peter Clements.
While it looks like a multi-lingual version of QuickPar,
there are some good features; Unicode characters, directory-tree,
faster repairing, smaller recovery files, batch scripting, and so on.
[ Feature ]
MultiPar supports both PAR 1.0 and PAR 2.0 specifications.
See "http://parchive.sourceforge.net/" for details of Parchive.
MultiPar uses UTF-8 or UTF-16 to treat filenames with non-ASCII characters.
While MultiPar and par2cmdline can treat sub-directory and UTF-8 filename,
QuickPar and other PAR2 clients cannot treat them.
Almost all PAR2 clients don't support UTF-16 filename and comment.
Be careful to use those special features.
[ System requirement ]
MultiPar requires a PC with Windows Vista or later (Windows 7, 8, 10).
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ Failure, Fault or Mistake ]
Use this application at your own risk, because I might miss something.
If you find something odd behavior, report the incident to me.
Some examples like output-log, screen-shot, file name, file size, your PC spec,
and detailes (when / where / what / how) are helpful to solve problems.
Please contain them as possible as you can.
Then, I will fix it at next version.
[ Security risk ]
You should treat PAR files in a same security level as their source files.
When you have secret data on some files and encrypt them,
you must create PAR files from their encrypted files.
If you create PAR files from non-encrypted files,
others may know how is the original secret data.
Even when there is no enough redundancy to recover it completely,
their PAR files may reveal useful information for a spy.
Parchive doesn't prevent an intended modification.
Recovering with unknown PAR files is same as copying unknown files on your PC.
The reliability of recovered files depends on their PAR files.
PAR clients may modify original valid files into something invalid files,
when PAR files were modified by a malicious cracker.
For example, if someone created PAR files from his modified source files,
the PAR files will damage your complete source files.
[ PAR 3.0 is not finished yet ]
PAR 3.0 in MultiPar is implemented only for personal testing purpose.
Because I modify its algorithm and format sometimes while writing the proposal,
current samples won't be compatible with future PAR 3.0 specifications.
Don't send current PAR3 files to others, who may not have the same version.
Currently sample PAR3 isn't available, while the specification is being updated.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ How to install or uninstall with installer package ]
Double click setup file ( MultiPar133_setup.exe or something like this name ),
and follow the installer dialog.
At version up, if you want to use previous setting, overwrite install is possible.
Before overwrite install, you should un-check "Integrate MultiPar into Shell".
You may need to re-start OS after overwrite install or uninstall rarely.
To install under "Program Files" or "Program Files (x86)" directory,
you must select "Install for all users" at the first dialog.
You can uninstall through the Windows OS's Control Panel,
or double click unins000.exe in a folder which MultiPar was installed.
Because uninstaller does not delete setting files or newly put files,
you may delete them by yourself.
When you have used installer package, you should not move install folder.
Or else, you will fail to uninstall later.
[ Installation for multiple users by installer package ]
If multiple users may log-on a PC, the administrator can install MultiPar for everyone.
By installing with administrative privileges, installer made Start Menu icon,
Desktop icon, and File association will be available for all users.
When he installed under "Program Files" directory, each user keeps individual setting.
When he installed in another folder, all users share same setting.
In either case, user made icons and association are available for the user only.
[ How to install with archive version ]
Unpack compressed file ( MultiPar133.zip or something like this name ) in a folder.
MultiPar.exe is the interface of MultiPar.
You can create short-cut icon or send-to link at Option window later.
If you associate PAR file extensions ".par" or ".par2" with MultiPar,
de-associate them from other application like QuickPar at first.
[ How to un-install with archive version ]
If you associate PAR file with MultiPar, de-associate them from this.
Delete all files in the install folder, in which you extract files.
If you installed MultiPar under "Program Files" directory,
setting data was saved in MultiPar folder under "Application Data" directory,
so you need to delete the folder.
When you integrated MultiPar into shell at Option window,
you must clear the check before un-install.
If you have deleted MultiPar.exe already, you can un-install the DLL manually.
Open "Command Prompt" and change directory to MultiPar's folder,
then type "RegSvr32.exe /u MultiParShlExt64.dll" to remove shell extension.
You cannot delete "MultiParShlExt64.dll", while it is used by OS or Explorer.
You may log-off and log-on again to OS before deleting the file.
[ How to change installed folder of archive version ]
Move files in the install folder.
If you associated PAR file with MultiPar, de-associate once, and associate again.
If you want to use same setting at another PC, copy the setting file "MultiPar.ini".
If you move MultiPar into "Program Files" directory,
setting data is saved in MultiPar folder under "Application Data" directory,
so you need to move "MultiPar.ini" into the folder, too.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ License ]
MultiPar consists of PAR clients and GUI to control them.
They are written by Yutaka Sawada.
Though console applications are open source (PAR clients are GPL),
GUI application is closed source.
Some article are available at my web site.
(URL: "https://hp.vector.co.jp/authors/VA021385/")
If you want source code, contact with me by e-mail.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ Support ]
I use GitHub issue's page as a web-forum for MultiPar users.
(URL: "https://github.com/Yutaka-Sawada/MultiPar/issues")
My name is Yutaka Sawada.
E-mail address is "tenfon (at mark) outlook.jp".
Or "multipar (at mark) outlook.jp" for PayPal usage and
"tenfon (at mark) users.sourceforge.net" for SourceForge users.
Because they use a same mail-box, don't send duplicate mails.
Though e-mail address had been "ten_fon (at mark) mail.goo.ne.jp" ago,
the mail service ended at March 2014, so don't send to there.
The (at mark) is format to avoid junk mails, and replace it with "@".
I get many spam mails from oversea.
If an e-mail is detected as junk mail or suspicious,
mail server may delete it automatically, and I won't see it.
[ Link ]
I use Vector 's author page to introduce MultiPar.
(URL: "https://hp.vector.co.jp/authors/VA021385/")
Because there is another official download page,
(URL: "https://www.vector.co.jp/soft/dl/winnt/util/se460801.html")
using direct link to files on the page isn't preferable.
When you write a link on somewhere, please don't include filename.
Restore damaged or lost files with PAR recovery files.
MultiPar (set of PAR clients and GUI)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ Introduction ]
MultiPar was made as an alternative to QuickPar.
The GUI is similar to QuickPar by getting agreement from Peter Clements.
While it looks like a multi-lingual version of QuickPar,
there are some good features; Unicode characters, directory-tree,
faster repairing, smaller recovery files, batch scripting, and so on.
[ Feature ]
MultiPar supports both PAR 1.0 and PAR 2.0 specifications.
See "http://parchive.sourceforge.net/" for details of Parchive.
MultiPar uses UTF-8 or UTF-16 to treat filenames with non-ASCII characters.
While MultiPar and par2cmdline can treat sub-directory and UTF-8 filename,
QuickPar and other PAR2 clients cannot treat them.
Almost all PAR2 clients don't support UTF-16 filename and comment.
Be careful to use those special features.
[ System requirement ]
MultiPar requires a PC with Windows Vista or later (Windows 7, 8, 10, 11).
[ Usage manual or Help documents ]
There are some usage manual or help documents in "help" folder.
English pages exists in "help/0409" folder.
You may open the manual by pushing "F1-key", while using MultiPar.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ Failure, Fault or Mistake ]
Use this application at your own risk, because I might miss something.
If you find something odd behavior, report the incident to me.
Some examples like output-log, screen-shot, file name, file size, your PC spec,
and detailes (when / where / what / how) are helpful to solve problems.
Please contain them as possible as you can.
Then, I will fix it at next version.
[ Security risk ]
You should treat PAR files in a same security level as their source files.
When you have secret data on some files and encrypt them,
you must create PAR files from their encrypted files.
If you create PAR files from non-encrypted files,
others may know how is the original secret data.
Even when there is no enough redundancy to recover it completely,
their PAR files may reveal useful information for a spy.
Parchive doesn't prevent an intended modification.
Recovering with unknown PAR files is same as copying unknown files on your PC.
The reliability of recovered files depends on their PAR files.
PAR clients may modify original valid files into something invalid files,
when PAR files were modified by a malicious cracker.
For example, if someone created PAR files from his modified source files,
the PAR files will damage your complete source files.
[ PAR 3.0 is not finished yet ]
PAR 3.0 in MultiPar is implemented only for personal testing purpose.
Because I modify its algorithm and format sometimes while writing the proposal,
current samples won't be compatible with future PAR 3.0 specifications.
Don't send current PAR3 files to others, who may not have the same version.
Currently sample PAR3 isn't available, while the specification is being updated.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ How to install or uninstall with installer package ]
Double click setup file ( MultiPar133_setup.exe or something like this name ),
and follow the installer dialog.
At version up, if you want to use previous setting, overwrite install is possible.
Before overwrite install, you should un-check "Integrate MultiPar into Shell".
You may need to re-start OS after overwrite install or uninstall rarely.
To install under "Program Files" or "Program Files (x86)" directory,
you must select "Install for all users" at the first dialog.
You can uninstall through the Windows OS's Control Panel,
or double click unins000.exe in a folder which MultiPar was installed.
Because uninstaller does not delete setting files or newly put files,
you may delete them by yourself.
When you have used installer package, you should not move install folder.
Or else, you will fail to uninstall later.
[ Installation for multiple users by installer package ]
If multiple users may log-on a PC, the administrator can install MultiPar for everyone.
By installing with administrative privileges, installer made Start Menu icon,
Desktop icon, and File association will be available for all users.
When he installed under "Program Files" directory, each user keeps individual setting.
When he installed in another folder, all users share same setting.
In either case, user made icons and association are available for the user only.
[ How to install with archive version ]
Unpack compressed file ( MultiPar133.zip or something like this name ) in a folder.
MultiPar.exe is the interface of MultiPar.
You can create short-cut icon or send-to link at Option window later.
If you associate PAR file extensions ".par" or ".par2" with MultiPar,
de-associate them from other application like QuickPar at first.
[ How to un-install with archive version ]
If you associate PAR file with MultiPar, de-associate them from this.
Delete all files in the install folder, in which you extract files.
If you installed MultiPar under "Program Files" directory,
setting data was saved in MultiPar folder under "Application Data" directory,
so you need to delete the folder.
When you integrated MultiPar into shell at Option window,
you must clear the check before un-install.
If you have deleted MultiPar.exe already, you can un-install the DLL manually.
Open "Command Prompt" and change directory to MultiPar's folder,
then type "RegSvr32.exe /u MultiParShlExt64.dll" to remove shell extension.
You cannot delete "MultiParShlExt64.dll", while it is used by OS or Explorer.
You may log-off and log-on again to OS before deleting the file.
[ How to change installed folder of archive version ]
Move files in the install folder.
If you associated PAR file with MultiPar, de-associate once, and associate again.
If you want to use same setting at another PC, copy the setting file "MultiPar.ini".
If you move MultiPar into "Program Files" directory,
setting data is saved in MultiPar folder under "Application Data" directory,
so you need to move "MultiPar.ini" into the folder, too.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ License ]
MultiPar consists of PAR clients and GUI to control them.
They are written by Yutaka Sawada.
Though console applications are open source (PAR clients are GPL),
GUI application is closed source.
Some article are available at my web site.
(URL: "https://hp.vector.co.jp/authors/VA021385/")
If you want source code, contact with me by e-mail.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
[ Support ]
I use GitHub issue's page as a web-forum for MultiPar users.
(URL: "https://github.com/Yutaka-Sawada/MultiPar/issues")
My name is Yutaka Sawada.
E-mail address is "tenfon (at mark) outlook.jp".
Or "multipar (at mark) outlook.jp" for PayPal usage and
"tenfon (at mark) users.sourceforge.net" for SourceForge users.
Because they use a same mail-box, don't send duplicate mails.
Though e-mail address had been "ten_fon (at mark) mail.goo.ne.jp" ago,
the mail service ended at March 2014, so don't send to there.
The (at mark) is format to avoid junk mails, and replace it with "@".
I get many spam mails from oversea.
If an e-mail is detected as junk mail or suspicious,
mail server may delete it automatically, and I won't see it.
[ Link ]
I use Vector 's author page to introduce MultiPar.
(URL: "https://hp.vector.co.jp/authors/VA021385/")
Because there is another official download page,
(URL: "https://www.vector.co.jp/soft/dl/winnt/util/se460801.html")
using direct link to files on the page isn't preferable.
When you write a link on somewhere, please don't include filename.

View File

Binary file not shown.

View File

Binary file not shown.